<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>城的灯</title>
  
  <subtitle>杨果的博客</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.yangguo.info/"/>
  <updated>2022-12-24T13:30:30.499Z</updated>
  <id>https://www.yangguo.info/</id>
  
  <author>
    <name>杨果</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>海量数据下的精准营销工程实践</title>
    <link href="https://www.yangguo.info/2020/08/22/%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E4%B8%8B%E7%9A%84%E7%B2%BE%E5%87%86%E8%90%A5%E9%94%80%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/"/>
    <id>https://www.yangguo.info/2020/08/22/海量数据下的精准营销工程实践/</id>
    <published>2020-08-22T02:59:32.000Z</published>
    <updated>2022-12-24T13:30:30.499Z</updated>
    
    <content type="html"><![CDATA[<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>精准营销从被提出到广泛运用，伴随着互联网技术飞速发展，尤其是大数据和人工智能技术的日趋成熟。精准营销也成为每一家互联网企业必然绕不开的课题，围绕它展开的业务不胜枚举。通常营销过程包含如下三部分：<br>① 营销活动前：确定营销的目标，<br>② 营销活动中：效果监控和跟踪，<br>③ 营销活动后：效果评估和优化。<br>本文主要分享笔者在精准营销工程化方面做的一些尝试和实践，侧重点在营销目标的确定。</p><hr><h3 id="现状"><a href="#现状" class="headerlink" title="现状"></a>现状</h3><p>营销目标就是我们传统意义的圈人，筛选出一批跟营销计划匹配的人。最初圈人逻辑是通过标签圈人，后续又加入了算法圈人，这两种模式的数据核心都基于离线数仓，按天更新。这两种方式在初期能够满足业务需求，但是随着业务发展及精细化运营的需求，该模式短板越来越明显，例如转化率的提升越来越难、实时性不高、灵活度不够等。希望有一套既能兼容现有离线链路，同时又能引入实时特征的方案。</p><hr><h3 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h3><p>离线链路侧，每个签约品牌每天会基于MaxCompute，通过协同过滤的方式从经济体的数亿用户中召回和品牌群像具有相似度的人，粗召回人数大品牌数亿中小品牌数千万。通过粗召回的数据量乘以签约品牌数，就可以很轻易的预估出离线召回的数据量在百亿级别，并且随着签约品牌的新增该数据量还会增加。实时链路侧，用户的实时特征，例如交易特征、LBS特征、商品加购特征、店铺点击特征等，每天特征总量也维持在几十亿级别，并且特征会随着业务的发展越来越多。<br>面对如此庞大的数据量，每条数据多耗时一毫秒，那么在百亿量级下就会放大到数千小时；一条数据多1bit，百亿就会放大到1G。所以特征数据如何传递、处理、存储都需要精心设计，否则带来的不但是程序性能上的急剧下降，而且还会导致计算和存储成本的飞速飙升，这便是我们在整个系统建设过程中遇到最大挑战。<br><a id="more"></a></p><hr><h3 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h3><p>通常召回策略有很多种，但是最常用的是基于内容匹配的召回和基于协同过滤的召回。基于内容配置的召回通常分为基于特征匹配和基于知识匹配，通常这种召回率较高，准确率低，适合冷启动环境。基于协同过滤的召回通常分为基于人、物、模型三种，通常这种召回准确度较高，但是存在冷启动问题。<br>我们的定位是离线协同过滤(模型)+离线特征+实时特征的多维度圈人，这样既整合了已有的成果，又引入了在线实时特征。由于离线协同过滤已经完成了基于品牌维度人的粗召回，再结合离线特征和实时特征便可以实现在线精召回。如果遇到冷启动问题，我们也可以利用已有的离线特征&amp;实时特征并结合品牌信息通过知识匹配来解决。<br>整体的方案详见下图：<br><img src="/images/promodws.png" alt="promodws"><br>上图不但描述了整个数据流程，而且也包含了部分技术选型，每个部分的职责如下。<br><code>离线模块</code>基于MaxCompute，绝大部分算法逻辑和所有的模型训练都在此完成，所有的离线特征都在此产出，数据通过OSS文件传递给上游。<code>实时模块</code>基于BLink，负责过滤和粗加工流式数据，数据分为平台数据和私域数据，所有的实时特征都在此产出，数据通过Kafka消息传递给上游。<code>数据服务模块</code>基于Java工程，其一是将离线和实时产出的特征写入Elasticsearch，其二是根据召回策略从Elasticsearch中查出符合规则的人再经过逻辑处理（算法二次打分/排序过滤）之后返回以供投放。<code>效果分析模块</code>复用底层的离线和实时能力，不仅仅是对投放效果跟踪分析，而且还为上层的投放决策和A/B Test提供数据支撑。<code>决策服务</code>是一个虚拟层，投放平台将营销活动的召回策略和投放策略告诉给数据服务，数据服务将召回的人按照投放决策来进行投放，可能会先试投一批人，如果转化率不够理想，投放决策就会停止后续的投放，从而调整召回决策或者算法逻辑然后再次尝试，直至达到预期目标。<code>投放平台</code>是整个产品的门面，商户运营人员都工作在此。</p><hr><h3 id="细节"><a href="#细节" class="headerlink" title="细节"></a>细节</h3><h4 id="多租户"><a href="#多租户" class="headerlink" title="多租户"></a>多租户</h4><p>SaaS离不开租户（品牌）的概念，不论是资源的分配、数据的安全，还是技术层面的分库分表。营销是需要成本的，特别是精准营销，需要大量的算力和存储来支撑；其次特定租户的营销具有低频、周期性的特点，让租户按需申请和结算，这样就降低了使用成本。如果租户没有营销活动时，我们还占用大量的资源去计算和存储，那么肯定会增加租户的沉没成本。当某一个品牌确定好营销时间段，我们会在营销活动前两天开始预热数据，Elasticsearch、Lindorm、Redis、MaxCompute、OSS、BLink等资源才开始真正被使用，这样就能做到按算力和存储的实际使用情况来收费。</p><h4 id="OLAP引擎"><a href="#OLAP引擎" class="headerlink" title="OLAP引擎"></a>OLAP引擎</h4><p>OLAP引擎我们选择了ES（Elasticsearch），Elastic Stack在大数据领域的优异表现大家有目共睹，选择Elasticsearch作为我们多维OLAP引擎最主要的原因分如下几点。</p><h5 id="Index-templates"><a href="#Index-templates" class="headerlink" title="Index templates"></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html" target="_blank" rel="noopener">Index templates</a></h5><p>由于多租户的特性，商户营销前系统会为其分配好ES，这个可以理解为分配库。库分配好之后还需要表，表对应的就是ES的索引，这个时候就需要有一套自动建表逻辑，这个自动建表逻辑就可以通过Index templates来实现。Template一般会包含如下信息，表分多少张（shards）、数据保存几份（replicas）、表的生命周期（lifecycle）、表中字段以及字段类型等诸多信息（dynamic field mapping）。有了Index templates就将索引创建逻辑简化到一行代码，创建索引的时候ES会自动根据需要创建的索引名称选择匹配template来创建索引。</p><h5 id="Dynamic-field-mapping"><a href="#Dynamic-field-mapping" class="headerlink" title="Dynamic field mapping"></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.8/dynamic-mapping.html" target="_blank" rel="noopener">Dynamic field mapping</a></h5><p>Schema-free的爽，大家或多或少都应该感受过。随着业务发展，表需要加字段这种情况估计大家都已经麻木了，尤其在数据分析领域，加字段不是一个两个的加而是几十几十的加。ES原生支持字段的自动创建及类型映射，字段类型是通过字段名来完成匹配。通过Dynamic field mapping + Index templates，便可以完全动态化的实现建表和加字段，极大提升了生产力和灵活度。</p><h5 id="Nested-datatype"><a href="#Nested-datatype" class="headerlink" title="Nested datatype"></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.8/nested.html" target="_blank" rel="noopener">Nested datatype</a></h5><p>传统的数据库里面，数据关系无外乎就三种，一对一、一对多和多对多。如果有数据有关联关系，通常通过主外键来建立数据联系，查询的时候通过join来获取完整信息。传统的关系型数据库中，很难做到一行记录中存储某个实体以及附属的信息。但是ES通过Nested数据类型提供了这种能力，它是NoSQL的一种高级特性。不但可以查询实体的时候直接带出附属信息，而是还可以在多个附属信息中查询满足条件的附属信息然后带出实体信息。例如用户（实体）一天内会有多个不同时间点的Geo Point（附属信息），一个签约品牌需要针对旗下的某几个门店做一场营销活动，需要选出最近3小时活跃在这些门店周边N公里的用户，不同的门店的筛选距离可能还不一样，nested type就能很完满的解决该问题。</p><hr><h4 id="特征存储"><a href="#特征存储" class="headerlink" title="特征存储"></a>特征存储</h4><h5 id="离线特征为什么不直接通过数据集成输出到ES？"><a href="#离线特征为什么不直接通过数据集成输出到ES？" class="headerlink" title="离线特征为什么不直接通过数据集成输出到ES？"></a>离线特征为什么不直接通过数据集成输出到ES？</h5><p>首先是算法产出特征名具有业务意义所以很长，特征字段名需要映射为正确类型的短字段名；其次是字段值也需要转化，需要转化为符合字段类型的值；最后是复杂的索引管理，数据处理完成之后还有各种针对索引的操作（alias管理、index切换、replica设置等）。这样做可以以最低成本来存储及传输，因为任何小的优化在海量数据下都会被放大，而且通过代码可以更加灵活的控制处理逻辑。</p><h4 id="离线特征产出存储我们选择了OSS，而不是输出到Lindorm-ADB等？"><a href="#离线特征产出存储我们选择了OSS，而不是输出到Lindorm-ADB等？" class="headerlink" title="离线特征产出存储我们选择了OSS，而不是输出到Lindorm/ADB等？"></a>离线特征产出存储我们选择了OSS，而不是输出到Lindorm/ADB等？</h4><p>不选择Lindorm和ADB等DB的原因，首先是不论写入还是读取所付出的性能损耗还是存储成本都要比OSS大的多，毕竟数据库需要维护自己特有的一套数据结构来支持数据管理；其次是业务并不需要查询，需要的是每一个Brand前一个周期算好的全量离线特征。所以我们直接通过MaxCompute的输出到外表（OSS），BrandId+DateTime作为分区输出即可。需要注意的是品牌太多可能会导致分区数超过MaxCompute的分区限制，可以通过每次跑一部分品牌，多次输出来解决。MaxCompute在输出OSS的时候，数据格式建议使用orc/parquet，tsv/csv半结构化数据膨胀非常厉害，它们之间存储大小的差异是数量级的。如果业务需要用tsv/csv，可以采用gzip压缩，从而来优化文件大小。</p><h5 id="实时特征为什么需要输出到Kafka？"><a href="#实时特征为什么需要输出到Kafka？" class="headerlink" title="实时特征为什么需要输出到Kafka？"></a>实时特征为什么需要输出到Kafka？</h5><p>主要原因就是Kafka相比RocketMQ/RabbbitMQ更适合大批量数据的写入，在大批量数据写入方面RocketMQ/RabbitMQ性能是不及Kafka的，RocketMQ/RabbitMQ更多的是关注在消息的安全/事务等特性。</p><hr><h3 id="数据服务"><a href="#数据服务" class="headerlink" title="数据服务"></a>数据服务</h3><h4 id="存储服务"><a href="#存储服务" class="headerlink" title="存储服务"></a>存储服务</h4><h5 id="职责"><a href="#职责" class="headerlink" title="职责"></a>职责</h5><ul><li>将离线/实时特征索引到ES</li><li>存储最近24小时的实时特征</li></ul><h5 id="索引切换流程"><a href="#索引切换流程" class="headerlink" title="索引切换流程"></a>索引切换流程</h5><p>为了简化索引生命周期管理以及提升索引性能，每当新的离线特征生成之时，我们会用新的离线特征生成一份新的索引，最后当新索引完成之后替换掉老索引。为了实现整个数据处理过程和索引切换过程业务方式无感知，ES别名（Alias）起到了关键性的作用，正是由于它的存在才让这一切变的成本低廉。为了清晰的阐述整个数据索引流程，我们拿某一个品牌的分片索引切换来说明，见下图：<br><img src="/images/index_switch_flow.png" alt="index_switch_flow"><br><code>user_feature_brand1</code> 业务查询所用别名<br><code>user_feature_brandId1_partition1_u</code> 实时特征更新更新所用别名<br><code>user_feature_brandId1_partition1_i</code> 离线特征写入和备份实时特征更新所用别名</p><h5 id="流程如何编排？"><a href="#流程如何编排？" class="headerlink" title="流程如何编排？"></a>流程如何编排？</h5><p>MaxCompute离线特征生成之后，存储服务会查询出每个ES集群所支持的品牌，并根据其优先级排好序。然后每个ES集群只为优先级最高的品牌开启一个ScheduleX网格计算任务，每个ScheduleX任务会根据品牌的OSS文件来划分子任务，上一个做完再做下一个。一个集群只跑一个品牌，主要是防止ES压力过大导致在线服务的不可用。使用ScheduleX作为计算框架是为了简化子任务的分发、重试、并发控制等逻辑，这样不但可以充分利用分布式的算力而且还让我们从复杂的状态管理中抽身而出，聚焦在主任务的编排上。</p><h5 id="为什么要保存最近24小时的实时特征？"><a href="#为什么要保存最近24小时的实时特征？" class="headerlink" title="为什么要保存最近24小时的实时特征？"></a>为什么要保存最近24小时的实时特征？</h5><p>虽然实时特征会实时写入到了ES之中，但是每次新的离线特征产出之后会触发新索引创建，新的索引中是没有实时特征的，所以我们需要使用备份的实时特征来更新到新的索引中。</p><h5 id="为什么需要每次都重建索引？"><a href="#为什么需要每次都重建索引？" class="headerlink" title="为什么需要每次都重建索引？"></a>为什么需要每次都重建索引？</h5><p>不拿新的离线特征直接去更新已有的索引呢，其一是因为更新索引比重建新索引的要慢很多；其二要更新就需要对比新老数据，从而知道哪些数据变化了、哪些数据删除了、哪些数据新增了，会让MaxCompute任务逻辑非常复杂。</p><h5 id="如何控制实时特征如何保存-如何控制生命周期？"><a href="#如何控制实时特征如何保存-如何控制生命周期？" class="headerlink" title="如何控制实时特征如何保存/如何控制生命周期？"></a>如何控制实时特征如何保存/如何控制生命周期？</h5><p>海量实时特征的存储，Lindorm是最佳的存储介质。它相较于传统DB，一是存储/扩容成本的优势，二是宽表/动态列的支持，三是数据精细的生命周期控制。任何事情都不是完美的，使用时特别要注意rowkey的设计，否则会产生数据倾斜、数据热点和查询受限等诸多问题。</p><h5 id="如何实现新旧实时特征融合及乐观锁控制？"><a href="#如何实现新旧实时特征融合及乐观锁控制？" class="headerlink" title="如何实现新旧实时特征融合及乐观锁控制？"></a>如何实现新旧实时特征融合及乐观锁控制？</h5><p>用备份的旧实时特征去更新新索引，但新的实时特征却还在不断的流入；其次消息乱序，新数据先到老数据后到；如何保证老数据不会覆盖新数据？该问题分两个维度来讨论，首先是ES层面的乐观锁控制，其实是Lindorm层面的乐观锁控制。ES层面，在开始读取备份实时特征之前，首先通过ES的Alias来开启新时特征新老索引的双写，然后再读取备份的实时特征去更新新的索引，也就是说此时新老数据都在同时更新新的索引。非时间型的实时特征在ES中会保存为两个字段，一个是特征值一个是特征时间，通过特征时间来做乐观锁控制，通过ES的Update By Query API来更新。Lindorm层面的乐观锁控制也比较简单，通过指定时间戳写入便可以实现乐观锁控制，老的时间戳是不能覆盖新的时间戳数据的。</p><h5 id="Update-By-Query命中率不高，很多无效的Query如何解决？"><a href="#Update-By-Query命中率不高，很多无效的Query如何解决？" class="headerlink" title="Update By Query命中率不高，很多无效的Query如何解决？"></a>Update By Query命中率不高，很多无效的Query如何解决？</h5><p>为了高效的插入，我们没有做_id层面的去重，所以更新的时候需要先查再更新。Update By Query相较于纯Update会多一次查询，别小看多的这一次查询，因为哪怕每条记录更新一毫秒，当乘以十亿的时候这就是相当恐怖的一个延迟了，并且这还是不考虑到一个人属于多个品牌的情况。这还不是问题的关键，问题的关键是不知道某个用户是否在某个品牌下，只有做一次查询才知道，如果存在就更新，如果不在就忽略，忽略的这次查询就是无效成本。例如当某个用户的经纬度发生了变化时，需要更新所有品牌下该用户的经纬度特征。<br>由于我们并不知道该用户是否在某个品牌下，所以对于中小品牌Update By Query会发现大量的Query少量的Update，出现这种情况是由于用户被算法粗筛排除在该品牌之外。大量无效的查询加重了ES的负载，是否存在成本更为低廉、性能更优的方式来替代ES查询，在更新之前就知道该用户是否存在于该品牌之下呢？布隆过滤器理所当然的成为了首选方案，为了解决分布式的需求，我们选择了阿里云的Redis布隆过滤器实现，不过需要注意的是受限于Redis单机4G内存的限制，如果存在空间需求大于2G的布隆过滤器，最好提前做好分片逻辑规划。有了布隆过滤器，在用品牌的离线数据创建索引的时候将用户的ID写入到品牌的布隆过滤器中，在后续的更新时，先去布隆过滤器中判断该用户是否存在，存在的话我们再去做Update By Query。</p><h5 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h5><p>存储服务是整个设计中难点所在。存储服务最大的瓶颈又在ES，为了获得极致的性能，当然离不开ES常规的性能优化，例如调大refresh_interval/translog .flush_threshold_size/translog.sync_interval、调低number_of_replicas、translog.durability同步改异步、bulk操作（client版本需大于7.5.0，否则死锁）等。性能只是众多技术细节中的一小部分，还有品牌优先级控制、索引模板设计、索引管理、布隆过滤器管理、Lindorm表结构设计、ES算力水位控制、数据生命周期管理等，都需要细致的考虑和设计。</p><h4 id="召回-amp-排序"><a href="#召回-amp-排序" class="headerlink" title="召回&amp;排序"></a>召回&amp;排序</h4><p>该服务主要分为查询场景管理和查询结果算法排序两部分。不同的查询场景就是不同的Query DSL，通过JEXL/Java Template来提供动态DSL能力。算法排序算子是通过一套规范的接口设计，让算法可以通过plugin的形式集成到业务逻辑中。</p><h5 id="为什么不用ES默认的JSON动态模板？"><a href="#为什么不用ES默认的JSON动态模板？" class="headerlink" title="为什么不用ES默认的JSON动态模板？"></a>为什么不用ES默认的JSON动态模板？</h5><p>JSON格式的DSL动态模板，只能支持简单的参数动态化，没有条件表达式（可选条件）及循环表达式（动态参数），而JEXL/Java Template却能完美的解决该问题。根据业务层传递的搜索条件，执行条件判断及动态参数赋值从而最终生成用于ES搜索用的DSL脚本。</p><h5 id="为什么还需要过滤-排序算子？"><a href="#为什么还需要过滤-排序算子？" class="headerlink" title="为什么还需要过滤/排序算子？"></a>为什么还需要过滤/排序算子？</h5><p>特殊情况下，通过ES召回的还需要再进行一次过滤/精排，这个时候需要跟算法同学共建，让算法同学的算法能够运行在工程代码之上，所以抽象出了一套过滤排序算子规范。</p><hr><h3 id="效果分析"><a href="#效果分析" class="headerlink" title="效果分析"></a>效果分析</h3><p>效果分析也分为实时和离线两条链路，实时链路主要用来做A/B Test和投放决策，离线链路主要负责产出投放效果报表所需的各种数据。好的效果分析离不开完善的埋点策略和数据采集工具支撑，否则就会出现数据映射偏差。</p><hr><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>上述所讲还是偏一些技术细节，为什么花大量的篇幅来阐述这些细节，因为任何一个小的缺陷在海量数据下都会放大。当然还有很多没有说到或者说明白，例如资源如何实现动态分配，效果分析如何影响投放决策等，后续有机会会一一补上。</p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h3&gt;&lt;p&gt;精准营销从被提出到广泛运用，伴随着互联网技术飞速发展，尤其是大数据和人工智能技术的日趋成熟。精准营销也成为每一家互联网企业必然绕不开的课题，围绕它展开的业务不胜枚举。通常营销过程包含如下三部分：&lt;br&gt;① 营销活动前：确定营销的目标，&lt;br&gt;② 营销活动中：效果监控和跟踪，&lt;br&gt;③ 营销活动后：效果评估和优化。&lt;br&gt;本文主要分享笔者在精准营销工程化方面做的一些尝试和实践，侧重点在营销目标的确定。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;现状&quot;&gt;&lt;a href=&quot;#现状&quot; class=&quot;headerlink&quot; title=&quot;现状&quot;&gt;&lt;/a&gt;现状&lt;/h3&gt;&lt;p&gt;营销目标就是我们传统意义的圈人，筛选出一批跟营销计划匹配的人。最初圈人逻辑是通过标签圈人，后续又加入了算法圈人，这两种模式的数据核心都基于离线数仓，按天更新。这两种方式在初期能够满足业务需求，但是随着业务发展及精细化运营的需求，该模式短板越来越明显，例如转化率的提升越来越难、实时性不高、灵活度不够等。希望有一套既能兼容现有离线链路，同时又能引入实时特征的方案。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;挑战&quot;&gt;&lt;a href=&quot;#挑战&quot; class=&quot;headerlink&quot; title=&quot;挑战&quot;&gt;&lt;/a&gt;挑战&lt;/h3&gt;&lt;p&gt;离线链路侧，每个签约品牌每天会基于MaxCompute，通过协同过滤的方式从经济体的数亿用户中召回和品牌群像具有相似度的人，粗召回人数大品牌数亿中小品牌数千万。通过粗召回的数据量乘以签约品牌数，就可以很轻易的预估出离线召回的数据量在百亿级别，并且随着签约品牌的新增该数据量还会增加。实时链路侧，用户的实时特征，例如交易特征、LBS特征、商品加购特征、店铺点击特征等，每天特征总量也维持在几十亿级别，并且特征会随着业务的发展越来越多。&lt;br&gt;面对如此庞大的数据量，每条数据多耗时一毫秒，那么在百亿量级下就会放大到数千小时；一条数据多1bit，百亿就会放大到1G。所以特征数据如何传递、处理、存储都需要精心设计，否则带来的不但是程序性能上的急剧下降，而且还会导致计算和存储成本的飞速飙升，这便是我们在整个系统建设过程中遇到最大挑战。&lt;br&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Elasticsearch" scheme="https://www.yangguo.info/tags/Elasticsearch/"/>
    
      <category term="精准营销" scheme="https://www.yangguo.info/tags/%E7%B2%BE%E5%87%86%E8%90%A5%E9%94%80/"/>
    
      <category term="BLink" scheme="https://www.yangguo.info/tags/BLink/"/>
    
      <category term="MaxCompute" scheme="https://www.yangguo.info/tags/MaxCompute/"/>
    
      <category term="Lindorm" scheme="https://www.yangguo.info/tags/Lindorm/"/>
    
      <category term="OSS" scheme="https://www.yangguo.info/tags/OSS/"/>
    
      <category term="Redis" scheme="https://www.yangguo.info/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>Elasticsearch Client Bulk 死锁问题处理</title>
    <link href="https://www.yangguo.info/2020/08/15/Elasticsearch%20Client%20Bulk%20%E6%AD%BB%E9%94%81%E9%97%AE%E9%A2%98%E5%A4%84%E7%90%86/"/>
    <id>https://www.yangguo.info/2020/08/15/Elasticsearch Client Bulk 死锁问题处理/</id>
    <published>2020-08-15T03:59:00.000Z</published>
    <updated>2020-08-15T13:03:20.670Z</updated>
    
    <content type="html"><![CDATA[<h3 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h3><p>最近工作中大量使用到Elasticsearch，需要通过Bulk操作将大量数据推到Elasticsearch中，但是发现有个别推数据的分布式任务一直不能结束，通过Thread Dump发现SchedulerX-ConsumerThread处于休眠状态，Thread Dump信息见下图：</p><p><img src="/images/es-bulk-deadlock.png" alt="es bulk deadlock"></p><a id="more"></a><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>通过上图的调用栈信息可知，SchedulerX-ConsumerThread在等待锁，而该锁又被 <em>elasticsearch[scheduler][T#1]</em> 所占有，通过<code>locked ownable synchronizers</code>可知它不但持有<code>ReentrantLock</code>而且还锁定了<code>ThreadPoolExecutor</code>， <em>elasticsearch[scheduler][T#1]</em> 不释放锁的原因是由于在等待一个信号量。分析到这里，就可以知道从哪儿入手了，接下来就可以进入到代码里面去看看到底发生了什么事情。</p><p>elasticsearch-rest-high-level-client 7.4.2版本中的BulkRequestHandler.java代码54行-&gt;92行如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">public void execute(BulkRequest bulkRequest, long executionId) &#123;</span><br><span class="line">        Runnable toRelease = () -&gt; &#123;&#125;;</span><br><span class="line">        boolean bulkRequestSetupSuccessful = false;</span><br><span class="line">        try &#123;</span><br><span class="line">            listener.beforeBulk(executionId, bulkRequest);</span><br><span class="line">            semaphore.acquire();</span><br><span class="line">            toRelease = semaphore::release;</span><br><span class="line">            CountDownLatch latch = new CountDownLatch(1);</span><br><span class="line">            retry.withBackoff(consumer, bulkRequest, ActionListener.runAfter(new ActionListener&lt;BulkResponse&gt;() &#123;</span><br><span class="line">                @Override</span><br><span class="line">                public void onResponse(BulkResponse response) &#123;</span><br><span class="line">                    listener.afterBulk(executionId, bulkRequest, response);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                @Override</span><br><span class="line">                public void onFailure(Exception e) &#123;</span><br><span class="line">                    listener.afterBulk(executionId, bulkRequest, e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;, () -&gt; &#123;</span><br><span class="line">                semaphore.release();</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;));</span><br><span class="line">            bulkRequestSetupSuccessful = true;</span><br><span class="line">            if (concurrentRequests == 0) &#123;</span><br><span class="line">                latch.await();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; catch (InterruptedException e) &#123;</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">            logger.info(() -&gt; new ParameterizedMessage(&quot;Bulk request &#123;&#125; has been cancelled.&quot;, executionId), e);</span><br><span class="line">            listener.afterBulk(executionId, bulkRequest, e);</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            logger.warn(() -&gt; new ParameterizedMessage(&quot;Failed to execute bulk request &#123;&#125;.&quot;, executionId), e);</span><br><span class="line">            listener.afterBulk(executionId, bulkRequest, e);</span><br><span class="line">        &#125; finally &#123;</span><br><span class="line">            if (bulkRequestSetupSuccessful == false) &#123;  // if we fail on client.bulk() release the semaphore</span><br><span class="line">                toRelease.run();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></p><p>通过代码可知，semaphore的释放是由ActionListener.runAfter来完成。也就是说是bulk flush成功那么就顺利释放，如果失败就进入retry逻辑，semaphore暂时不释放，得等到retry之后再判断是否释放。由于flush和retry公用一个scheduler，每个scheduler会有一个线程池，加上前面 <em>elasticsearch[scheduler][T#1]</em> 锁定了一个<code>ThreadPoolExecutor</code>，所以问题的核心点随之浮出水面。</p><p>elasticsearch-rest-high-level-client 7.4.2版本中的Scheduler.java代码56行-&gt;63行如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">static ScheduledThreadPoolExecutor initScheduler(Settings settings) &#123;</span><br><span class="line">    final ScheduledThreadPoolExecutor scheduler = new SafeScheduledThreadPoolExecutor(1,</span><br><span class="line">            EsExecutors.daemonThreadFactory(settings, &quot;scheduler&quot;), new EsAbortPolicy());</span><br><span class="line">    scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);</span><br><span class="line">    scheduler.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);</span><br><span class="line">    scheduler.setRemoveOnCancelPolicy(true);</span><br><span class="line">    return scheduler;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>通过上面的代码可知，线程池只有一个线程，flush任务占用了一个线程就是上面的 <em>elasticsearch[scheduler][T#1]</em> ，导致retry任务没有线程资源可用，但是retry的任务却又占有信号量，所以这样便完成了一个环状等待。这是一种高级的死锁，不能简单的通过锁的依赖来排查到循环等待的情况。</p><p>当我们分析出问题的关键点，那么就简单了。通过Github和Stack Overflow一搜会发现bulk操作长久以来都存在这种问题，最新的<a href="https://github.com/elastic/elasticsearch/pull/48697" target="_blank" rel="noopener">pull request</a>能够解决该问题，只需要升级client到7.5.0及以上即可。</p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;背景&quot;&gt;&lt;a href=&quot;#背景&quot; class=&quot;headerlink&quot; title=&quot;背景&quot;&gt;&lt;/a&gt;背景&lt;/h3&gt;&lt;p&gt;最近工作中大量使用到Elasticsearch，需要通过Bulk操作将大量数据推到Elasticsearch中，但是发现有个别推数据的分布式任务一直不能结束，通过Thread Dump发现SchedulerX-ConsumerThread处于休眠状态，Thread Dump信息见下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/es-bulk-deadlock.png&quot; alt=&quot;es bulk deadlock&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Elasticsearch" scheme="https://www.yangguo.info/tags/Elasticsearch/"/>
    
      <category term="死锁" scheme="https://www.yangguo.info/tags/%E6%AD%BB%E9%94%81/"/>
    
  </entry>
  
  <entry>
    <title>TCP RST引发的一场血案</title>
    <link href="https://www.yangguo.info/2018/04/19/TCP%20RST%E5%BC%95%E5%8F%91%E7%9A%84%E4%B8%80%E5%9C%BA%E8%A1%80%E6%A1%88/"/>
    <id>https://www.yangguo.info/2018/04/19/TCP RST引发的一场血案/</id>
    <published>2018-04-19T13:11:00.000Z</published>
    <updated>2018-04-19T14:47:36.723Z</updated>
    
    <content type="html"><![CDATA[<p>最近利用业余时间写了一个简单的分布式对象存储<a href="https://github.com/chengdedeng/yfs" target="_blank" rel="noopener">yfs</a>，分布式一致性构建在<a href="https://github.com/atomix/atomix" target="_blank" rel="noopener">atomix</a>之上。atomix实现了<a href="https://raft.github.io/" target="_blank" rel="noopener">raft</a>，并且提供了更高层次的抽象，例如Map、Set、DistributedLock等。为了详细的阐述遇到问题的来龙去脉，有必要简单阐述下yfs的架构，架构图如下：</p><p><img src="/images/yfs_architecture.jpg" alt="yfs_architecture"></p><a id="more"></a><p>从上图可知yfs由Gateway和分组的Store两部分组成，Gateway主要负责路由、鉴权、流控、安全等非存储功能，Store主要负责存储。每个Group至少由三个Store节点组成，这三个Store所存储的数据一模一样，也就是说每个文件至少有三个备份。Gateway也至少有三个节点，Store节点会自动上报metadata给Gateway，Gateway根据Store节点上报的信息来调整自己的路由策略。Gateway是在我的例外一个开源项目<a href="https://github.com/chengdedeng/waf" target="_blank" rel="noopener">waf</a>的基础上改造的而来的，每个Store节点都运行着一个Springboot服务，用来提供上传和下载服务。</p><h1 id="问题一"><a href="#问题一" class="headerlink" title="问题一"></a>问题一</h1><p>当上传的文件大于Store允许上传的文件size时，发现前端一直在等待响应结果，HTTP请求传递过程是<code>Browser-&gt;Gateway-&gt;Store</code>。</p><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><ol><li>首先尝试在Store上debug上传接口，发现请求压根就没有进入到controller，一搜发现<a href="https://stackoverflow.com/questions/21089106/converting-multipartfile-to-java-io-file-without-copying-to-local-machine" target="_blank" rel="noopener">https://stackoverflow.com/questions/21089106/converting-multipartfile-to-java-io-file-without-copying-to-local-machine</a>里面已经说的很清楚，原来只有等文件上传完成之后，才会进入到controller中。</li><li>那为什么没有上传完成呢，wireshark一抓包发现，当数据上传部分之后，Tomcat TCP端口突然发送了一个<strong>RST</strong>，为什么会出现这种情况呢？一查原来当Tomcat发现上传的文件大于允许上传的文件时，Tomcat就直接RST TCP链接。其实原因很简单，既然上传的文件不符合规范，后面的数据包即使发送过来也会被扔掉，那与其这样，不如不要上传了，还节省上传宝贵的网络带宽。思路是没有错，但是这样HTTP层面就拿不到任何数据，浏览器最多报一个网络异常，例如Chrome会出现<strong>net::ERR_CONNECTION_RESET</strong>。假如我们允许上传的最大文件是10M，如果上传的文件大于10M小于20M，我们希望返回一个http response告诉前端上传的文件不符合规范，20M以上我们就认为是异常流量或者攻击直接RST TCP。这样的方案既方便测试，又对使用者友好，那该如何实施呢？</li><li>Tomcat的配置参数<strong>MaxSwallowSize</strong>从意思上来说已经非常清晰，由于Store节点运行的是Springboot服务，所以需要重新配置<strong>TomcatEmbeddedServletContainerFactory</strong>。设置之后经测试发现20M以下的，前端会迅速收到响应结果，但是20M以上的还是存在问题，因为20M以上的Tomcat还是会RST TCP。</li><li>通过netstat发现Gateway-&gt;Store之间的链路RST之后，Browser-&gt;Gateway之间的链路并没有断开，看来问题的关键就在这里了。发现问题的关键点之后，改起来就简单，我在Gateway上给Gateway-&gt;Store之间的channel加了一个CloseFuture Listener从而去关闭Browser-&gt;Gateway之间的channel，经测试发现问题解决。</li></ol><h1 id="问题二"><a href="#问题二" class="headerlink" title="问题二"></a>问题二</h1><p>正当我准备逛下论坛庆祝一下的时候，运维部署到了测试环境，结果任何请求Nginx都报<code>upstream prematurely closed connection while reading upstream</code>，顿时我就懵逼了。</p><h1 id="分析-1"><a href="#分析-1" class="headerlink" title="分析"></a>分析</h1><p>测试环境HTTP请求传递过程是<code>Browser-&gt;Nginx-&gt;Gateway-&gt;Store</code>，跟我开发环境不太一样，多了一层Nginx，通过错误信息可以知道Nginx的upstream也就是Gateway出现了问题。</p><ol><li>connection close，那么必然会出现TCP关闭，在Nginx服务器上经过抓包一看，发现Gateway主动关闭了Nginx-&gt;Gateway之间的TCP通道。到Gateway服务器上抓包，发现Gateway先关闭了Gateway-&gt;Store之间的通道，然后由于CloseFuture Listener接着关闭Nginx-&gt;Gateway之间的通道。Gateway每次都主动发起关闭，为什么会这样呢？我本地从来没有出现过这样的现象，Gateway和Store之间应该是长连接才对啊。</li><li>仔细分析整个链路，先是Browser-&gt;Nginx，发现走的HTTP1.1，没有任何问题，但是Nginx-&gt;Gateway却走的HTTP1.0，看来问题的关键就出在这个地方了。原来当Store返回响应结果之后，由于HTTP1.0是短链接，所以Gateway主动发起关闭。由于我前面给Gateway-&gt;Store的channel加了关闭的监听，所以Nginx-&gt;Gateway之间的channel也会立即关闭，所以导致Nginx会报上面的错误。</li><li><p>接着检查了Nginx的配置，果然发现Nginx到Gateway之前HTTP1.1的配置不正确，加入如下配置之后问题解决。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">proxy_http_version 1.1;</span><br><span class="line">proxy_set_header Connection &quot;&quot;;</span><br></pre></td></tr></table></figure></li><li><p>所以<code>CloseFuture Listener</code>方案只能工作在HTTP1.1之上，也就是说Gateway-&gt;Store之间channel的关闭，只可能是由于idle超时或者异常关闭，否者就不应选择上面的方案。</p></li></ol><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>网络问题的定位，通常都不是非常容易，但是如果通过wireshark等抓包工具来配合研究协议和报文，一般会事半功倍。TCP和HTTP是值得投入时间来学习和研究的协议，因为它们就是迷雾中的灯塔，虽然你不知道岸在何处，但是你却知道没有偏离航向，到达彼岸只是时间问题。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近利用业余时间写了一个简单的分布式对象存储&lt;a href=&quot;https://github.com/chengdedeng/yfs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;yfs&lt;/a&gt;，分布式一致性构建在&lt;a href=&quot;https://github.com/atomix/atomix&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;atomix&lt;/a&gt;之上。atomix实现了&lt;a href=&quot;https://raft.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;raft&lt;/a&gt;，并且提供了更高层次的抽象，例如Map、Set、DistributedLock等。为了详细的阐述遇到问题的来龙去脉，有必要简单阐述下yfs的架构，架构图如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/yfs_architecture.jpg&quot; alt=&quot;yfs_architecture&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Http" scheme="https://www.yangguo.info/tags/Http/"/>
    
      <category term="Proxy" scheme="https://www.yangguo.info/tags/Proxy/"/>
    
      <category term="ShadowSocks" scheme="https://www.yangguo.info/tags/ShadowSocks/"/>
    
      <category term="Netty" scheme="https://www.yangguo.info/tags/Netty/"/>
    
      <category term="LittleProxy" scheme="https://www.yangguo.info/tags/LittleProxy/"/>
    
      <category term="TCP" scheme="https://www.yangguo.info/tags/TCP/"/>
    
      <category term="Http1.0" scheme="https://www.yangguo.info/tags/Http1-0/"/>
    
      <category term="Http1.1" scheme="https://www.yangguo.info/tags/Http1-1/"/>
    
      <category term="YFS" scheme="https://www.yangguo.info/tags/YFS/"/>
    
      <category term="Atomix" scheme="https://www.yangguo.info/tags/Atomix/"/>
    
  </entry>
  
  <entry>
    <title>Classloader之三方库class overriding</title>
    <link href="https://www.yangguo.info/2018/02/25/ClassLoader_ClassOverriding/"/>
    <id>https://www.yangguo.info/2018/02/25/ClassLoader_ClassOverriding/</id>
    <published>2018-02-25T12:00:00.000Z</published>
    <updated>2018-02-26T14:55:51.210Z</updated>
    
    <content type="html"><![CDATA[<h3 id="上下文"><a href="#上下文" class="headerlink" title="上下文"></a>上下文</h3><p>假如我们项目中引入的三方包出现了一个小的bug，我们通常会fixed它，然后创建一个pull request，但是通常我们等不到pull request被merge之后的release版本。<br>这种现象非常普遍，对于这种情况，我们通过有如下三种方案：</p><ol><li>使用merge了pull request的snapshot版本。</li><li>自己fork一个分支，发布内部版本。</li><li>Class Overriding，重写之后的代码就放到自己的项目中。</li></ol><p>方案一会引入了很多不稳定的因素到项目之中，方案二自己维护一个临时的稳定分支成本太高，所以它们都不是非常好的方案。方案三则避免了前两种方案的不足，并且等官方release版本发布之后，可以零成本的切换到官方版本。</p><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>Class Overriding之后，在Classpath中会存在两个同名的该类，一个是位于自己项目中另一个是三方库中的原有的。那么问题就来，Classloader究竟会加载谁呢？<br>Classloader Hierarchy大家都比较熟悉，网上也有大量的文章来阐述。我们不但要清楚parent delegation model，而且需要明白<code>相同的Class被不同的Classloader加载，在JVM中它们也是不同的Class</code>。显然此处这并不是我们想要分析的重点，除非我们bug刚好出现在Custemclassloader，也就是说我们要分析的是同一个Classloader对Classpath下名称相同但位置不同的Class资源的加载顺序。</p><a id="more"></a><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p>要分析该问题，我们需要给JVM配置上<strong>-verbose:class</strong>，从而将Class的加载信息打印出来。我们需要分为四种场景来分析：</p><ol><li>IDE环境</li><li>Fat Jar（Spring Boot）</li><li>War</li><li>Jar/JSW（Java Service Wrapper）</li></ol><p>为了测试上述的五种情况，我分别用<a href="https://github.com/chengdedeng/waf" target="_blank" rel="noopener">WAF</a>和<a href="https://github.com/chengdedeng/spring-boot-loader-play" target="_blank" rel="noopener">spring-boot-loader-play</a>两份代码来进行测试。WAF中使用了<a href="https://github.com/adamfisk/LittleProxy" target="_blank" rel="noopener">LitteProxy</a>，由于<strong>org.littleshoot.proxy.impl.ProxyToServerConnection</strong>在连接Socks5 Server存在一个bug，正好可以用来作为测试，该测试代码可以覆盖场景1和场景4。spring-boot-loader-play其实是文章<a href="https://dzone.com/articles/spring-boot-classloader-and-class-override" target="_blank" rel="noopener">Spring Boot Classloader and Class Overriding</a>的测试代码，它可以覆盖场景2。有1、2、4的分析，场景3其实就可以不用测试了，如果感兴趣，自己动手试试就知道了。</p><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><h4 id="场景1"><a href="#场景1" class="headerlink" title="场景1"></a>场景1</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$1 from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$2 from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$3 from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$4 from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyConnection$ResponseReadMonitor from file:/Users/guo/.m2/repository/org/littleshoot/littleproxy/1.1.2/littleproxy-1.1.2.jar]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$5 from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$6 from file:/Users/guo/work/code/waf/target/classes/]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyConnection$RequestWrittenMonitor from file:/Users/guo/.m2/repository/org/littleshoot/littleproxy/1.1.2/littleproxy-1.1.2.jar]</span><br><span class="line">[Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$7 from file:/Users/guo/work/code/waf/target/classes/]</span><br></pre></td></tr></table></figure><h4 id="场景4"><a href="#场景4" class="headerlink" title="场景4"></a>场景4</h4><h5 id="classpath中waf-1-0-SNAPSHOT-jar在littleproxy-1-1-2-jar之前的启动命令"><a href="#classpath中waf-1-0-SNAPSHOT-jar在littleproxy-1-1-2-jar之前的启动命令" class="headerlink" title="classpath中waf-1.0-SNAPSHOT.jar在littleproxy-1.1.2.jar之前的启动命令"></a>classpath中waf-1.0-SNAPSHOT.jar在littleproxy-1.1.2.jar之前的启动命令</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/bin/java -server -Xms128m -Xmx128m -Xmn60m -XX:+UseG1GC -Xloggc:/tmp/log/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -verbose:class -Djava.library.path=lib -classpath lib/wrapper.jar:conf:lib/waf-1.0-SNAPSHOT.jar:lib/littleproxy-1.1.2.jar:lib/commons-lang3-3.5.jar:lib/barchart-udt-bundle-2.3.0.jar:lib/netty-all-4.1.17.Final.jar:lib/mitm-2.1.4.jar:lib/bcprov-jdk15on-1.56.jar:lib/bcpkix-jdk15on-1.56.jar:lib/spring-context-4.2.5.RELEASE.jar:lib/spring-aop-4.2.5.RELEASE.jar:lib/aopalliance-1.0.jar:lib/spring-beans-4.2.5.RELEASE.jar:lib/spring-core-4.2.5.RELEASE.jar:lib/spring-expression-4.2.5.RELEASE.jar:lib/slf4j-api-1.7.21.jar:lib/slf4j-log4j12-1.7.7.jar:lib/log4j-1.2.17.jar:lib/metrics-core-3.1.2.jar:lib/metrics-graphite-3.1.2.jar:lib/metrics-log4j-3.1.2.jar:lib/metrics-jvm-3.1.2.jar:lib/metrics-spring-3.1.3.jar:lib/metrics-healthchecks-3.1.2.jar:lib/metrics-annotation-3.1.2.jar:lib/spring-context-support-4.1.6.RELEASE.jar:lib/esapi-2.1.0.1.jar:lib/commons-configuration-1.10.jar:lib/commons-lang-2.6.jar:lib/commons-beanutils-core-1.8.3.jar:lib/commons-fileupload-1.3.1.jar:lib/commons-io-2.2.jar:lib/commons-collections-3.2.2.jar:lib/xom-1.2.5.jar:lib/xml-apis-1.3.03.jar:lib/xercesImpl-2.8.0.jar:lib/xalan-2.7.0.jar:lib/bsh-core-2.0b4.jar:lib/antisamy-1.5.3.jar:lib/nekohtml-1.9.16.jar:lib/commons-httpclient-3.1.jar:lib/batik-css-1.8.jar:lib/batik-ext-1.8.jar:lib/batik-util-1.8.jar:lib/xml-apis-ext-1.3.04.jar:lib/guava-20.0.jar:lib/httpclient-4.5.3.jar:lib/commons-logging-1.2.jar:lib/commons-codec-1.9.jar:lib/httpcore-4.4.6.jar:lib/httpmime-4.5.2.jar:lib/joda-time-2.9.9.jar -Dwrapper.key=SDxB55oX2r5RDERn -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.pid=27529 -Dwrapper.version=3.2.3 -Dwrapper.native_library=wrapper -Dwrapper.service=TRUE -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1 org.tanukisoftware.wrapper.WrapperSimpleApp info.yangguo.waf.Application</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$1 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$2 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$3 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$4 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyConnection$ResponseReadMonitor from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$5 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$6 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyConnection$RequestWrittenMonitor from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 16:42:23 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$7 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/waf-1.0-SNAPSHOT.jar]</span><br></pre></td></tr></table></figure><h5 id="classpath中waf-1-0-SNAPSHOT-jar在littleproxy-1-1-2-jar之后的启动命令"><a href="#classpath中waf-1-0-SNAPSHOT-jar在littleproxy-1-1-2-jar之后的启动命令" class="headerlink" title="classpath中waf-1.0-SNAPSHOT.jar在littleproxy-1.1.2.jar之后的启动命令"></a>classpath中waf-1.0-SNAPSHOT.jar在littleproxy-1.1.2.jar之后的启动命令</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/bin/java -server -Xms128m -Xmx128m -Xmn60m -XX:+UseG1GC -Xloggc:/tmp/log/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -verbose:class -Djava.library.path=lib -classpath lib/wrapper.jar:conf:lib/littleproxy-1.1.2.jar:lib/waf-1.0-SNAPSHOT.jar:lib/commons-lang3-3.5.jar:lib/barchart-udt-bundle-2.3.0.jar:lib/netty-all-4.1.17.Final.jar:lib/mitm-2.1.4.jar:lib/bcprov-jdk15on-1.56.jar:lib/bcpkix-jdk15on-1.56.jar:lib/spring-context-4.2.5.RELEASE.jar:lib/spring-aop-4.2.5.RELEASE.jar:lib/aopalliance-1.0.jar:lib/spring-beans-4.2.5.RELEASE.jar:lib/spring-core-4.2.5.RELEASE.jar:lib/spring-expression-4.2.5.RELEASE.jar:lib/slf4j-api-1.7.21.jar:lib/slf4j-log4j12-1.7.7.jar:lib/log4j-1.2.17.jar:lib/metrics-core-3.1.2.jar:lib/metrics-graphite-3.1.2.jar:lib/metrics-log4j-3.1.2.jar:lib/metrics-jvm-3.1.2.jar:lib/metrics-spring-3.1.3.jar:lib/metrics-healthchecks-3.1.2.jar:lib/metrics-annotation-3.1.2.jar:lib/spring-context-support-4.1.6.RELEASE.jar:lib/esapi-2.1.0.1.jar:lib/commons-configuration-1.10.jar:lib/commons-lang-2.6.jar:lib/commons-beanutils-core-1.8.3.jar:lib/commons-fileupload-1.3.1.jar:lib/commons-io-2.2.jar:lib/commons-collections-3.2.2.jar:lib/xom-1.2.5.jar:lib/xml-apis-1.3.03.jar:lib/xercesImpl-2.8.0.jar:lib/xalan-2.7.0.jar:lib/bsh-core-2.0b4.jar:lib/antisamy-1.5.3.jar:lib/nekohtml-1.9.16.jar:lib/commons-httpclient-3.1.jar:lib/batik-css-1.8.jar:lib/batik-ext-1.8.jar:lib/batik-util-1.8.jar:lib/xml-apis-ext-1.3.04.jar:lib/guava-20.0.jar:lib/httpclient-4.5.3.jar:lib/commons-logging-1.2.jar:lib/commons-codec-1.9.jar:lib/httpcore-4.4.6.jar:lib/httpmime-4.5.2.jar:lib/joda-time-2.9.9.jar -Dwrapper.key=Q2nBncFVdPijJ62c -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.pid=27838 -Dwrapper.version=3.2.3 -Dwrapper.native_library=wrapper -Dwrapper.service=TRUE -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1 org.tanukisoftware.wrapper.WrapperSimpleApp info.yangguo.waf.Application</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$1 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$2 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$3 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$4 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyConnection$ResponseReadMonitor from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$5 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$6 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyConnection$RequestWrittenMonitor from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded org.littleshoot.proxy.impl.ProxyToServerConnection$7 from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/littleproxy-1.1.2.jar]</span><br><span class="line">INFO   | jvm 1    | 2018/02/26 17:07:01 | [Loaded com.google.common.net.HostAndPort from file:/Users/guo/work/code/waf/target/waf-1.0-SNAPSHOT/lib/guava-20.0.jar]</span><br></pre></td></tr></table></figure><p>启动命令中jar的顺序控制，如果是手写命令很简单，想怎么写都行，JSW包中只需要改conf/wrapper.conf文件中wrapper.java.classpath.<code>n</code>，n就是顺序，所以改动更加方便。</p><h4 id="场景2"><a href="#场景2" class="headerlink" title="场景2"></a>场景2</h4><p>fat jar有多种格式，此处我们只分析一下springboot fat jar，这是最复杂的一种情况。文章<a href="https://dzone.com/articles/spring-boot-classloader-and-class-override" target="_blank" rel="noopener">Spring Boot Classloader and Class Overriding</a>花了大篇幅来阐述其中的门门道道。一切都是由于Springboot项目打的fat包不是一个标准的jar包，对于非标准的jar来说，Class的加载肯定是需要自定义Classloader的，Springboot就是通过<a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java" target="_blank" rel="noopener">LaunchedURLClassLoader</a>来解决该问题。正是由于该LaunchedURLClassLoader的引入，导致复杂度提升。作者使用gradle+springboot plugin来构建，不但麻烦而且最后的方案还是一个半成品，我又使用maven+jsw来构建，则可以完全避开作者文章中的各种麻烦，详见<a href="https://github.com/chengdedeng/classloadertest" target="_blank" rel="noopener">classloadertest</a>。所以很多时候换一种思路，则是柳暗花明。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ol><li>IDE环境下，Classloader优先加载本机编译路径下的class。</li><li>如果是普通jar包，class的加载顺序就是classpath下资源给出的顺序。在顺序的控制上，可以自己写脚本把需要优先加载的放在前面，而jsw会自动把当前项目的jar放在第一位，所以极其方便。</li><li>War包WEB-INF/classes下的资源优先于lib中的jar加载。</li><li>JSW是我认为最优雅。</li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;上下文&quot;&gt;&lt;a href=&quot;#上下文&quot; class=&quot;headerlink&quot; title=&quot;上下文&quot;&gt;&lt;/a&gt;上下文&lt;/h3&gt;&lt;p&gt;假如我们项目中引入的三方包出现了一个小的bug，我们通常会fixed它，然后创建一个pull request，但是通常我们等不到pull request被merge之后的release版本。&lt;br&gt;这种现象非常普遍，对于这种情况，我们通过有如下三种方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用merge了pull request的snapshot版本。&lt;/li&gt;
&lt;li&gt;自己fork一个分支，发布内部版本。&lt;/li&gt;
&lt;li&gt;Class Overriding，重写之后的代码就放到自己的项目中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;方案一会引入了很多不稳定的因素到项目之中，方案二自己维护一个临时的稳定分支成本太高，所以它们都不是非常好的方案。方案三则避免了前两种方案的不足，并且等官方release版本发布之后，可以零成本的切换到官方版本。&lt;/p&gt;
&lt;h3 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h3&gt;&lt;p&gt;Class Overriding之后，在Classpath中会存在两个同名的该类，一个是位于自己项目中另一个是三方库中的原有的。那么问题就来，Classloader究竟会加载谁呢？&lt;br&gt;Classloader Hierarchy大家都比较熟悉，网上也有大量的文章来阐述。我们不但要清楚parent delegation model，而且需要明白&lt;code&gt;相同的Class被不同的Classloader加载，在JVM中它们也是不同的Class&lt;/code&gt;。显然此处这并不是我们想要分析的重点，除非我们bug刚好出现在Custemclassloader，也就是说我们要分析的是同一个Classloader对Classpath下名称相同但位置不同的Class资源的加载顺序。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Classloader" scheme="https://www.yangguo.info/tags/Classloader/"/>
    
      <category term="Springboot" scheme="https://www.yangguo.info/tags/Springboot/"/>
    
      <category term="JSW" scheme="https://www.yangguo.info/tags/JSW/"/>
    
  </entry>
  
  <entry>
    <title>API Gateway的开源解决方案那么多，为什么我们却还要选择自研？</title>
    <link href="https://www.yangguo.info/2018/01/11/Gateway/"/>
    <id>https://www.yangguo.info/2018/01/11/Gateway/</id>
    <published>2018-01-11T01:00:00.000Z</published>
    <updated>2018-01-20T10:27:49.154Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>API Gateway/Backend for Front-End作为一种目前非常流行并且经过验证的Pattern，不论是在Netflix/Amazon还是BAT都得到了广泛的应用。在<a href="https://en.wikipedia.org/wiki/Microservices" target="_blank" rel="noopener">Microservice architecture pattern</a>大行其道的当下，API Gateway的建设显得尤为重要，本文主要是分享笔者在API Gateway研发中的一些心得体会，同时这篇文章也发布在了公众号<strong>聊聊架构</strong>，可以移步<a href="https://mp.weixin.qq.com/s/OSzGUse2yBFHgmGhSVhBIw" target="_blank" rel="noopener">此处</a>查看讨论。</p></blockquote><hr><h3 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h3><p>为了阐述API Gateway建设带来的收益是值得我们投入大成本来研发的，我们假设需要建设一个<strong>online store</strong>。为了支撑整个电子商务逻辑的运转，可以简单抽象成如下的微服务：</p><ol><li>shopping cart</li><li>shipping</li><li>inventory</li><li>recommendation</li><li>order</li><li>review</li><li>product catalog</li></ol><p>架构如下图所示：</p><p><img src="/images/gateway1.png" alt="gateway architecture"></p><a id="more"></a><p>Gateway的职责如下：</p><ol><li>安全</li><li>流控</li><li>缓存</li><li>鉴权</li><li>监控</li><li>日志</li><li>协议转换</li><li>…..</li></ol><p>可以想象在<strong>Microservice</strong>的架构体系之下，如果没有统一的地方去处理这些跟业务关联度不是很紧密但是又必不可少的逻辑，那么每个上面需求将会在每个<strong>Microservice</strong>都实现一遍，这种代价非常的高昂。当然任何事物都存在双面性，有好就必然有坏的一面，流量全部都要经过Gateway，所以它一定要非常稳定且高可用，假如一旦此处出现问题，那么可以想象问题的严重性。</p><hr><h3 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h3><p>技术选型从来就不是一件容易的事情，虽然笔者没有所谓的选择恐惧症，但是想到要拦截所有的7层流量，心里面还是难免有点犯怵。业界开源的方案有<a href="https://github.com/Netflix/zuul" target="_blank" rel="noopener">Netflix/zuul</a>，<a href="https://github.com/Kong/kong" target="_blank" rel="noopener">Kong/kong</a>，<a href="https://github.com/openresty/openresty" target="_blank" rel="noopener">openresty/openresty</a>+<code>自研</code>，<a href="https://github.com/TykTechnologies/tyk" target="_blank" rel="noopener">TykTechnologies/tyk</a>，<a href="https://github.com/netty/netty" target="_blank" rel="noopener">Netty</a>+<code>自研</code>。这些都是非常不错的方案，并且已经经过了大量的生产实践，那么剩下的事情就只有根据自身的实际情况来选择。经过仔细分析，我们希望API Gateway足够薄，不希望在其上附加太多的功能。如果单纯的从性能的角度来考虑，<strong>Kong</strong> / <strong>Openresty</strong> 估计是最好的，毕竟底层都是Nginx。虽然在Openresty平台上做过一些小的模块，但是毕竟在这个上面算不上专家。<strong>Zuul</strong> 是Java平台上非常好的选择之一，有Netflix和Pivotal的支持并且功能强大，但是考虑到跟Netflix的全家桶集成很紧密，组件很多且复杂度较高，我们的系统主要还是架构的<code>Dubbo</code>之上，Spring Cloud用的地方不多。该项目的发起经历了两个阶段，初期我们是想做<a href="https://en.wikipedia.org/wiki/Web_application_firewall" target="_blank" rel="noopener">WAF</a>把我们系统的安全给提升上来，并没有一上来就想做API Gateway，虽然这两者从整体架构上来说是没有本质上的差异。随着WAF的研发完成，我们觉得这一层可以做更多的事情，这才有了API Gateway的构想。随后我们在WAF的基础之上逐步新增了很多功能，就如后面我们会讨论的架构图所示，WAF的拦截最终变成了我们Filter中的一环。当时由于笔者有多年使用<code>Netty</code>的经验，所以我们在设计WAF是就选用Netty。</p><p>考虑到<strong>Netty</strong>还是太偏底层，所以选择了构建在<strong>Netty</strong>之上的<a href="https://github.com/adamfisk/LittleProxy" target="_blank" rel="noopener">LittleProxy</a>作为我们的HTTP Proxy，这样就避免了我们完全重头开发。<strong>LitteProxy</strong> 的Programmers也是<a href="https://www.getlantern.org/" target="_blank" rel="noopener">LANTERN</a>的维护者。关于蓝灯的介绍，可以移步<a href="https://zh.wikipedia.org/wiki/%E8%93%9D%E7%81%AF" target="_blank" rel="noopener">维基百科</a>。LittleProxy只有二十来个类十来个接口，从头到尾通读一遍代码也就2/3天的时间，并且从GitHub Star数和Stack Overflow的收录数量来看，它都是不错的选择。作为API Gateway中最重要的部分确定之后，其实剩下的困难点就不多了。</p><p>如果当时我们一开始就想好了要构建一套完整的API Gateway，是不是现在就是例外的选择了，也许是也许不是，但是这些都不重要，重要的是选择了就得尽最大的努力让你别为自己的选择而后悔！</p><h3 id="架构设计"><a href="#架构设计" class="headerlink" title="架构设计"></a>架构设计</h3><p>在讨论我们的架构设计之前，先让我们来欣赏下zuul的架构，如下图所示：</p><p><img src="/images/gateway2.png" alt="zuul architecture"></p><p>上图中<code>Zuul Servlet</code>是Http流量的接入点，Zuul从2.x和3x分支也开始使用Netty作为它们网络通讯的核心。<code>ZuulFilter Runner</code>是Gateway的业务核心，Filter type分为三种类型，<code>Pre routing</code>/<code>Routing</code>/<code>Post routing</code>，其实就是我们所谓的<a href="https://en.wikipedia.org/wiki/Aspect-oriented_programming" target="_blank" rel="noopener">AOP</a>。虽然ZuulFilter被分为了三种类型，但是其实它们是共享一个Request Content，这一点非常重要，ZullFilter的执行流程如下图：</p><p><img src="/images/gateway3.png" alt="ZuulFilter"></p><p>一个HTTP Request进来首先要通过pre filter的检查，如果没有任何问题，就交给routing filter让其把流量转发给origin server，然后origin server会返回响应结果给下游的post filter。以上两步任何一步出现问题就直接到error filter，error filter也会将流量转给post filter。最终都是由post filter来对response进行整形，然后返回给上游的代理，最终到达用户。Zuul为了方便用户定制并且动态化加载配置引入了Groovy，这是非常好的地方，即我们享受到静态化的性能，同时又能享受到动态化的灵活。右侧和底部的设计是用来管理Zuul相关配置的，包含服务的注册发现、Filter的管理、统计分析相关的很多东西。</p><p>分析完Zuul之后，我们丛中吸收了很多东西，下图是我们的架构：</p><p><img src="/images/gateway4.jpg" alt="waf architecture"></p><p>从上图可以看出，我们精简了Zuul从而更关注其核心的部分，因为我们的目的不是构建一套功能强大的中间件，而是一套非常精简的API Gateway。</p><hr><h3 id="研发过程"><a href="#研发过程" class="headerlink" title="研发过程"></a>研发过程</h3><p>WAF我已经开源出来，有兴趣的朋友可以把玩下，地址为<a href="https://github.com/chengdedeng/waf" target="_blank" rel="noopener">https://github.com/chengdedeng/waf</a>。该项目非常精简，真正核心的类估计也就5个左右，非常适合阅读和练习。关于WAF的技术过程中遇到的问题，我也写了两篇文章来说明，详见<a href="https://www.yangguo.info/2017/06/06/Java版WAF实现/">Java版WAF实现细节</a>和<a href="https://www.yangguo.info/2017/11/13/HttpProxy研发心得/">HttpProxy研发心得</a>。WAF中的有些特性其实在API Gateway中用到的概率较小，比如ShadowSocks/Socks5的支持，如果这些大家不需要可以移除，这样代码就更少更精简。如果想让WAF要真正的成为API Gateway，还有很多的事情要做，例如Zuul中groovy自定义Filter以及管理功能，这些都需要根据需求定制。我们内部的版本也正在逐步完善，一些我觉得有用的特性也会同步到WAF中去。目前我们的API Gateway还非常简单，例如路由我们目前只支持Host，不支持URL的regex，而且很多管理功能都还处于开发阶段，但是从目前运行的情况来看还是非常稳定。</p><p>对于自研Gateway，其实难度还是不小，并不像看上去的那么美。如果对网络编程不是很熟悉，特别对TCP/HTTP理解不是比较全面，建议要慎重，因为这样风险太大。我们在研发过程就碰见很多的问题，下面就简单的列举几个：</p><ol><li>HTTP Header头的大小写问题</li><li>CORS OPTIONS不受XHR的控制</li><li>Netty堆外内存泄漏</li><li>Origin Server心跳检测</li><li>Request Content-Type数据传递的区别</li><li>根据URL Regex路由</li><li>……</li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>网络上关于kong/zuul的文章非常多，关于自研的却不是很多，所以分享出来供大家参考，希望大家在做方案的时候有更多的选择和思考。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;API Gateway/Backend for Front-End作为一种目前非常流行并且经过验证的Pattern，不论是在Netflix/Amazon还是BAT都得到了广泛的应用。在&lt;a href=&quot;https://en.wikipedia.org/wiki/Microservices&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Microservice architecture pattern&lt;/a&gt;大行其道的当下，API Gateway的建设显得尤为重要，本文主要是分享笔者在API Gateway研发中的一些心得体会，同时这篇文章也发布在了公众号&lt;strong&gt;聊聊架构&lt;/strong&gt;，可以移步&lt;a href=&quot;https://mp.weixin.qq.com/s/OSzGUse2yBFHgmGhSVhBIw&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;此处&lt;/a&gt;查看讨论。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;Context&quot;&gt;&lt;a href=&quot;#Context&quot; class=&quot;headerlink&quot; title=&quot;Context&quot;&gt;&lt;/a&gt;Context&lt;/h3&gt;&lt;p&gt;为了阐述API Gateway建设带来的收益是值得我们投入大成本来研发的，我们假设需要建设一个&lt;strong&gt;online store&lt;/strong&gt;。为了支撑整个电子商务逻辑的运转，可以简单抽象成如下的微服务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;shopping cart&lt;/li&gt;
&lt;li&gt;shipping&lt;/li&gt;
&lt;li&gt;inventory&lt;/li&gt;
&lt;li&gt;recommendation&lt;/li&gt;
&lt;li&gt;order&lt;/li&gt;
&lt;li&gt;review&lt;/li&gt;
&lt;li&gt;product catalog&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;架构如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/gateway1.png&quot; alt=&quot;gateway architecture&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Http" scheme="https://www.yangguo.info/tags/Http/"/>
    
      <category term="TLS" scheme="https://www.yangguo.info/tags/TLS/"/>
    
      <category term="Socks5" scheme="https://www.yangguo.info/tags/Socks5/"/>
    
      <category term="Proxy" scheme="https://www.yangguo.info/tags/Proxy/"/>
    
      <category term="ShadowSocks" scheme="https://www.yangguo.info/tags/ShadowSocks/"/>
    
      <category term="Netty" scheme="https://www.yangguo.info/tags/Netty/"/>
    
      <category term="LittleProxy" scheme="https://www.yangguo.info/tags/LittleProxy/"/>
    
  </entry>
  
  <entry>
    <title>HttpProxy研发心得</title>
    <link href="https://www.yangguo.info/2017/11/13/HttpProxy%E7%A0%94%E5%8F%91%E5%BF%83%E5%BE%97/"/>
    <id>https://www.yangguo.info/2017/11/13/HttpProxy研发心得/</id>
    <published>2017-11-13T06:34:00.000Z</published>
    <updated>2017-12-13T00:14:53.828Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>笔者之前为了研究HttpProxy，自己用Java造了一个轮子<a href="https://github.com/chengdedeng/waf" target="_blank" rel="noopener">WAF</a>，随着研究的深入，该项目也逐步成为公司的API Gateway之一，在研发过程中收获很多。</p></blockquote><h3 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h3><p>既然初衷是HttpProxy，那么HTTP(RFC7230-7235/7216)协议是必然需要了解，这是每个开发人员都或多或少了解的协议。我之前自认为对HTTP还是有足够的了解的，但是经过此次实际运用才发现还有很多细节理解的不够。</p><ol><li>Header头不区分大小写，而我之前所有的获取header都是一律大写，导致在不同的浏览器表现不一致，有些头拿不到。</li><li>Http Method了解不够，我们跨域方案采用的是CORS，所以会有OPTIONS请求去询问后端是否支持跨域。用户登录成功之后会返回一个Token，后续的访问都需要在HEAD中带上该标识，由于发起OPTIONS请求是浏览器的自带行为不是XHR请求，所以没有Token。Gateway对所有的请求都会进行拦截，自然这个OPTIONS就不幸命中，解决方法就是本地Cookie也写一份或者OPTIONS请求直接放行。</li><li>X-Forwarded-For，X-Real-IP使用场景和限制没有充分弄清楚。</li></ol><a id="more"></a><h3 id="MITM-TLS"><a href="#MITM-TLS" class="headerlink" title="MITM/TLS"></a>MITM/TLS</h3><p>JSSE(Java Secure Socket Extension)之前只是略有接触没有详细研究，为了让Gateway支持TLS/MITM，花了我较多的精力。</p><ol><li>JDK7/JDK8 JCE的标准不一样，可以参见<a href="https://www.java.com/zh_CN/download/faq/release_changes.xml" target="_blank" rel="noopener">链接</a>，所以导致加密套件和加密长度的不同，反正一句话就是建议大家别用JDK7就对了。</li><li>自签名证书和CA签发证书，对于浏览器来说问题都不大，最多就是信任然后就可以继续访问，但是对于后端代码来说不太一样。由于CA的证书是受信任的，但是自签名的却不受代码信任，所以代码中我们需要自定义TrustManager，相当于需要将用户在浏览器中手动点击信任的动作用代码给自动化。</li><li>MITM中间人拦截，其实就是支持TLS的代理服务器，但是得注意浏览器是不支持开启了HSTS的网站，这个花了我很长时间，开始我以为是不支持开启了HTTP/2的网站，这个我还不敢完全肯定，因为<a href="http://www.jd.com是可以的，但是www.v2ex.com却不幸。支持和不支持的见下面两张图。我刚开始是想用TLS代理来翻墙玩玩，但是其效果非常差，一是加解密非常耗时，二是流量特征明显，不过作为HTTPS抓包工具还是有点用处的。" target="_blank" rel="noopener">www.jd.com是可以的，但是www.v2ex.com却不幸。支持和不支持的见下面两张图。我刚开始是想用TLS代理来翻墙玩玩，但是其效果非常差，一是加解密非常耗时，二是流量特征明显，不过作为HTTPS抓包工具还是有点用处的。</a><br> <img src="/images/MITM1.png" alt="MITM1"><br> <img src="/images/MITM2.png" alt="MITM2"></li></ol><h3 id="ShadowSocks-Socks5"><a href="#ShadowSocks-Socks5" class="headerlink" title="ShadowSocks/Socks5"></a>ShadowSocks/Socks5</h3><p>由于业务需求，国内需要访问Amazon和Google的SaaS服务，ShadowSocks这是毫无疑问的选择，目标是国内的程序可以通过HTTP的形式访问国外的SaaS服务，该问题困扰了我好几天。</p><ol><li>Socks5的握手原理不清晰导致Proxy到目标机的Channel创建之后，我才在Pipeline中加入了Socks5ProxyHandler，也就是说握手已经完成，我把Socks Server当成了下游的Http Proxy，也是由于LittleProxy有ProxyChain的原因对我产生了误导。</li><li><p>Wireshark中抓包发现Tcp Window Update，这个会出现在Socks握手之时，当时认为ACK的序号不对，其实是Len=0的包也要加一，对TCP协议的理解还需要加强，下面两张图详细阐述了SS的握手过程。</p><p> <img src="/images/TcpWindowUpdate.png" alt="TcpWindowUpdate"><br> <img src="/images/socks5handshake.png" alt="socks5handshake"></p></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;笔者之前为了研究HttpProxy，自己用Java造了一个轮子&lt;a href=&quot;https://github.com/chengdedeng/waf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WAF&lt;/a&gt;，随着研究的深入，该项目也逐步成为公司的API Gateway之一，在研发过程中收获很多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;HTTP协议&quot;&gt;&lt;a href=&quot;#HTTP协议&quot; class=&quot;headerlink&quot; title=&quot;HTTP协议&quot;&gt;&lt;/a&gt;HTTP协议&lt;/h3&gt;&lt;p&gt;既然初衷是HttpProxy，那么HTTP(RFC7230-7235/7216)协议是必然需要了解，这是每个开发人员都或多或少了解的协议。我之前自认为对HTTP还是有足够的了解的，但是经过此次实际运用才发现还有很多细节理解的不够。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Header头不区分大小写，而我之前所有的获取header都是一律大写，导致在不同的浏览器表现不一致，有些头拿不到。&lt;/li&gt;
&lt;li&gt;Http Method了解不够，我们跨域方案采用的是CORS，所以会有OPTIONS请求去询问后端是否支持跨域。用户登录成功之后会返回一个Token，后续的访问都需要在HEAD中带上该标识，由于发起OPTIONS请求是浏览器的自带行为不是XHR请求，所以没有Token。Gateway对所有的请求都会进行拦截，自然这个OPTIONS就不幸命中，解决方法就是本地Cookie也写一份或者OPTIONS请求直接放行。&lt;/li&gt;
&lt;li&gt;X-Forwarded-For，X-Real-IP使用场景和限制没有充分弄清楚。&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Http" scheme="https://www.yangguo.info/tags/Http/"/>
    
      <category term="TLS" scheme="https://www.yangguo.info/tags/TLS/"/>
    
      <category term="Socks5" scheme="https://www.yangguo.info/tags/Socks5/"/>
    
      <category term="Proxy" scheme="https://www.yangguo.info/tags/Proxy/"/>
    
      <category term="ShadowSocks" scheme="https://www.yangguo.info/tags/ShadowSocks/"/>
    
      <category term="Netty" scheme="https://www.yangguo.info/tags/Netty/"/>
    
      <category term="LittleProxy" scheme="https://www.yangguo.info/tags/LittleProxy/"/>
    
  </entry>
  
  <entry>
    <title>Java版WAF技术细节</title>
    <link href="https://www.yangguo.info/2017/06/06/Java%E7%89%88WAF%E5%AE%9E%E7%8E%B0/"/>
    <id>https://www.yangguo.info/2017/06/06/Java版WAF实现/</id>
    <published>2017-06-06T08:03:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近一直在研究应用安全相关的技术和产品，因为随着产品不断推广放量，安全体系的建设已经刻不容缓。谈到应用安全大家想到最多就是各种商业级的WAF防火墙，但是这个不是我想谈的重点，这次我们主要谈谈软件WAF。说到软件WAF，大家最熟悉的就是<a href="https://modsecurity.org/" target="_blank" rel="noopener">ModSecurity</a>、<a href="https://github.com/ESAPI/esapi-java-legacy" target="_blank" rel="noopener">esapi-java-legacy</a>、<a href="https://github.com/p0pr0ck5/lua-resty-waf" target="_blank" rel="noopener">lua-resty-waf</a>、<a href="https://github.com/loveshell/ngx_lua_waf" target="_blank" rel="noopener">ngx_lua_waf</a>等众多解决方案。虽然笔者所在公司使用了以上方案中的一种，但是为了研究和学习，笔者用Java重新造了一个轮子，本篇文章主要是阐述笔者设计和研发的思路，供大家参考。项目地址为：<a href="https://github.com/chengdedeng/waf。" target="_blank" rel="noopener">https://github.com/chengdedeng/waf。</a></p><a id="more"></a><h3 id="HTTP代理"><a href="#HTTP代理" class="headerlink" title="HTTP代理"></a>HTTP代理</h3><p>上面提到的软件WAF，除了esapi-java-legacy之外，其余几种都是基于代理模式。代理的优点就在于对WEB应用没有任何侵入，对业务的干扰几乎为零，这种方案是毫无疑问是最应该被采纳的。ModSecurity基于Apache，lua-resty-waf和ngx_lua_waf基于Nginx，Apache和Nginx作为业界最优秀的开源Proxy，它们的性能及稳定性是毫无疑问的。Java之上要想构建一套稳定可靠的网络通讯，必然绕不开Netty，所以在HTTP代理层面我选择了基于Netty研发的<a href="https://github.com/adamfisk/LittleProxy" target="_blank" rel="noopener">LittleProxy</a>，它的programmers也是<a href="https://www.getlantern.org/" target="_blank" rel="noopener">LANTERN</a>的维护者。关于蓝灯的介绍，可以移步<a href="https://zh.wikipedia.org/wiki/%E8%93%9D%E7%81%AF" target="_blank" rel="noopener">维基百科</a>。</p><p>HTTP代理分为正向和反向两种，作为应用防火墙，肯定要选择反向模式。由于LittleProxy原生支持Proxy Chain，所以WAF提供了透明模式和反向代理两种模式。透明模式就是使用LittleProxy的ChainProxyManager，把真正目标机的映射交给下游的Proxy，反向代理就是WAF直接管理目标机的映射关系。透明代理最为简单，只需要实现ChainedProxyManager就行，但是对于反向代理却需要考虑loadbalance和心跳检测。特别需要注意的是，当节点不健康的时候需要将节点从路由表中移除，当节点恢复健康状态，要能够及时的将节点新增到路由表,在WAF中的具体实现可以参考<a href="https://github.com/chengdedeng/waf/blob/master/src/main/java/info/yangguo/waf/HostResolverImpl.java" target="_blank" rel="noopener">HostResolverImpl</a>。</p><p>由于HTTP1.1支持chunked，对于client传递上来的数据别尝试配置较大的buffer，从而获取FullHttpRequest。因为当有大文件上传时，当你配置的buffer放不整个文件时，就会导致请求失败，在Nett的ChannelPipeline上也需要移除<strong>inflater</strong>和<strong>aggregator</strong>两个handler。为了支持TLS，可以自己实现SslEngineSource，WAF中的实现可以参考<a href="https://github.com/chengdedeng/waf/blob/master/src/main/java/info/yangguo/waf/util/SelfSignedSslEngineSource2.java" target="_blank" rel="noopener">SelfSignedSslEngineSource2</a>，WAF的证书是自签名的，大家可以换成CA签名的即可，浏览器就不会报不可信任了。</p><p>要写一个稳定可靠的HTTP代理服务器，虽然已经有了Netty和LittleProxy，但是还远远不够。一定要仔细研究HTTP协议，开源程序只是协议的实现，协议才是核心中的核心。因为一旦对协议理解的不是很清楚，你就可能给自己埋一个坑，所以在协议的研究上多花点时间是绝对值得，并且可以取得事半功倍的效果。</p><h3 id="安全"><a href="#安全" class="headerlink" title="安全"></a>安全</h3><p>应用程序的数据安全，在WAF中被分为两种类型，一种是进来的数据另外一种是出去的数据，也就是Request和Response，它们分别对应<strong>HttpRequestFilterChain</strong>和<strong>HttpResponseFilterChain</strong>责任链。</p><p>RequestFilter又分为黑名单Filter和白名单Filter，Request拦截又分为黑白名单两种,Response拦截主要给输出的数据进行安全加固.在Request的拦截规则方面,我参考了loveshell/ngx_lua_waf.<br>Request Content-Type又为<code>form-data</code>,<code>x-www-form-urlencoded</code>、<code>raw</code>、<code>binary</code>等多种类型，所以这里又牵涉到是否需要对url decode，同时还要考虑大小写的问题，只有方方面面了解透了，才能做到较高质量的防护。Response作为一种可扩展的增强，例如已经有不可信的数据进入系统，拦截已经晚了，这时我们就可以通过Response进行转义之后输出，或者做一些例如防止Clickjack的攻击。安全方面因为笔者能力和精力有限，只做了极少的一部分，需要大家仔细阅读例如<a href="https://www.owasp.org/images/5/51/OWASP_Top_10_2013-Chinese-V1.2.pdf" target="_blank" rel="noopener">OWASP_Top_10</a>之类的文章来完善防护细节。</p><h3 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h3><p>作为中间代理服务器，性能肯定是大多数人会首先关心的，所以我做了一个基准测试，具体的测试数据见<a href="https://github.com/chengdedeng/waf#性能" target="_blank" rel="noopener">GitHub</a>。从测试数据可以看出，Java的Proxy跟Nginx比，<br>的确有不小的差距，但是我觉得作为一个无状态的中间件，只要性能不是太差同时能够水平扩展，那么这个中间件就是可以被接受的。enjoy yourself!</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近一直在研究应用安全相关的技术和产品，因为随着产品不断推广放量，安全体系的建设已经刻不容缓。谈到应用安全大家想到最多就是各种商业级的WAF防火墙，但是这个不是我想谈的重点，这次我们主要谈谈软件WAF。说到软件WAF，大家最熟悉的就是&lt;a href=&quot;https://modsecurity.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ModSecurity&lt;/a&gt;、&lt;a href=&quot;https://github.com/ESAPI/esapi-java-legacy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;esapi-java-legacy&lt;/a&gt;、&lt;a href=&quot;https://github.com/p0pr0ck5/lua-resty-waf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;lua-resty-waf&lt;/a&gt;、&lt;a href=&quot;https://github.com/loveshell/ngx_lua_waf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ngx_lua_waf&lt;/a&gt;等众多解决方案。虽然笔者所在公司使用了以上方案中的一种，但是为了研究和学习，笔者用Java重新造了一个轮子，本篇文章主要是阐述笔者设计和研发的思路，供大家参考。项目地址为：&lt;a href=&quot;https://github.com/chengdedeng/waf。&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/chengdedeng/waf。&lt;/a&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="架构、HTTP、安全" scheme="https://www.yangguo.info/tags/%E6%9E%B6%E6%9E%84%E3%80%81HTTP%E3%80%81%E5%AE%89%E5%85%A8/"/>
    
  </entry>
  
  <entry>
    <title>红星悦家技术研发心得</title>
    <link href="https://www.yangguo.info/2017/03/17/%E7%BA%A2%E6%98%9F%E4%BA%92%E8%81%94%E7%BD%91%E8%BD%AC%E5%9E%8B%E7%9A%84%E6%8A%80%E6%9C%AF%E5%BF%83%E5%BE%97/"/>
    <id>https://www.yangguo.info/2017/03/17/红星互联网转型的技术心得/</id>
    <published>2017-03-17T04:54:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>这篇文章最早发布在<a href="http://mp.weixin.qq.com/s?__biz=MzA5Nzc4OTA1Mw==&amp;mid=2659599087&amp;idx=1&amp;sn=b113e5fbfccd8145a61f026250e08081&amp;chksm=8be997fdbc9e1eebaba52b4b2a0606acfb50f78ee5267d3b8912866039960f31f57438bfbd54&amp;mpshare=1&amp;scene=5&amp;srcid=0324MRCZlO2zYn4Ly3TZhr4D#rd" target="_blank" rel="noopener">聊聊架构</a>微信公众号，结果引发了大家的众多评论，有对红星所做事情的疑惑也有对技术合理性的质疑。不论哪种情况，很高兴抛出了一个让大家还有兴趣聊两句的话题，还是那句话不求共鸣，只求博君一笑，不论是嘲笑还是微笑！</p></blockquote><p>互联网和移动互联网的飞速崛起，对传统行业造成了破坏性的冲击和几乎窒息的压迫，迫使他们必须做出改变和应对。在这个都想拿到船票的时刻，技术的重要性已经可以媲美商业模式，因为再好的商业模式都离不开技术来落地，所以谁的技术领先，谁就有可能最快抵达终点。但是众所周知，传统行业的IT部都是支撑部门，在信息化建设的研发能力上是先天不足的。红星美凯龙是一家超过30年的老牌传统企业，虽然过去几年在信息化的建设和互联网的探索上投入了大量的资本，但是在技术方面的积淀收效甚微。随着红星的港股上市，再次开启互联网战略，成立红星悦家互联网集团，我们在吸收了过去经验教训的基础上，重新架构红星的技术体系。</p><a id="more"></a><h2 id="研发团队组建"><a href="#研发团队组建" class="headerlink" title="研发团队组建"></a>研发团队组建</h2><p>研发团队的组建一直以来困扰着诸多创业公司，对于红星来说也不例外。虽然红星在线下有较强的优势，但是对于互联网确实不折不扣的新兵，每一位候选人都带着深深的疑惑和担忧，让我们感到在吸引优秀技术人才上的无力感。在内部推荐、猎头、人事三管齐下的努力下，技术团队迅速壮大起来，因为我们都在努力传达一个讯息，那就是红星对互联网业务是认真的。不论是做人还是做事，就怕认真，我们在努力给技术人员创造舒适工作环境的同时也让技术人员拥有更多的话语权。任何事情都是两面性的，快速扩张的团队在磨合上出现了不小的问题，这不是最突出的，难点在突然发现出现了技术栈的分裂。我们在一开始就设计了基于Java的整套后端解决方案，由于种种原因，部分核心业务团队开始使用PHP。这个时候，我们开始意识到问题的严重性，当然并不是因为否定PHP是世界上最好的语言，而是我们发现这样会完全打乱我们早期的整体架构规划。对于一家创业公司，如果很多基础设施不能做到公用不能求同存异，研发成本和技术架构复杂度会陡然上升，吃过苦头的公司不在少数，当然这也是他们吹牛的资本。在技术管理层的强烈要求下，该业务团队很快切换了过来，不论是人员招聘还是新需求的研发上都进行了管控。红星技术团队初期遇到的磨合问题，跟所有迅速扩展组建的技术团队一样，大家来至五湖四海四海八荒，不论是技术决策还是业务需求，我们都本着对事不对人，所以很快就度过了那段最为动荡的时光。</p><h2 id="新架构的设计与落地"><a href="#新架构的设计与落地" class="headerlink" title="新架构的设计与落地"></a>新架构的设计与落地</h2><p>对于重装归来的红星悦家，我们希望在技术方面能够夯实每一步，所以在技术团队组建之初就开始了架构设计。在新技术和新架构层出不穷的当下，在技术选型和技术方案的敲定上，始终秉承着<code>不熟不用</code>。没有成功案例、开源者的背景不好、社区活跃度低、团队接收度低的都不采用。我们要的不是屌炸天，而是稳定可靠易用，当然我们也不是独断专行，而是兼蓄并包，只要团队成员的选择能够通过架构委员会的challenge。整体架构大概如下：</p><p><img src="/images/mmall_architecture.png" alt="红星架构"></p><p>虽然我们内心保守，但毕竟新业务没有太多的历史负担，加上大家都来至行业内技术口碑都还不错的公司，所以在短短的两个月之内就攒出来了这样一套并不算太过时的架构。</p><h4 id="监控"><a href="#监控" class="headerlink" title="监控"></a>监控</h4><p>监控的重要性不言而喻，一开始我们就将监控摆在了首要位置，不论是容量规划、服务的Qos、代码质量管理，都需要监控作为支撑。在网络、负载、IO、流量等常规监控上，我们选择了Zabbix。在日志分析和报表展示上，我们选择了Kibana，它是我们日志平台的核心组件。在业务逻辑的Metrics上，我们选择了Graphite，代码端我们使用了Dropwizard的Metrics库再配合metrics-spring，实现了埋点的最小侵入性。Graphite中的Metrics数据在Grafana进行Dashboard展现，同时Graphite的REST接口提供数据给Zabbix进行业务Metrics报警。当某个核心业务逻辑的Rate、响应时长超过预定阀值时就会触发。在服务治理上，我们选择了Dubbo，在调用链的追踪上，开始我们选择了自研，代号为Dragon，通过Dubbo的Attachment将TraceId和Parent SpanId带给RPC下一跳，就能将整个调用链给串起来。Dragon的界面如下，从截图中能够清晰的了解Dragon传达的信息，线上的数值表示网络耗时，红色表示异常，绿色表示正常。</p><p><img src="/images/dragon1.png" alt="dragon1"></p><p><img src="/images/dragon2.png" alt="dragon2"></p><p><img src="/images/dragon3.png" alt="dragon3"></p><p><img src="/images/dragon4.png" alt="dragon4"></p><p>同时我们修改Log4j MDC将TraceId和日志进行了绑定。由于Kibana对日志的切割和索引，我们通过TraceId就能够很轻松的将一次跨域多个服务的Trace所打印的日志给搜索出来。虽然Dragon看起来不错，但是我们发现Pinpoint做的更多更好，并且通过Java agent侵入性几乎为零，所以最终我们放弃了自研的Dragon。调用链的追踪可以认为是微观粒度的，是分析某一个请求，宏观层面我们还是依靠Metrics和Dubbo自带的monitor。由于Pinpoint有丰富的插件，所以收集的信息非常多，这也是我们选择它的主要原因。</p><p><img src="/images/pinpoint2.png" alt="pinpoint2"></p><p><img src="/images/pinpoint3.png" alt="pinpoint3"></p><p><img src="/images/pinpoint4.png" alt="pinpoint4"></p><h4 id="技术细节"><a href="#技术细节" class="headerlink" title="技术细节"></a>技术细节</h4><p><code>对外服务层</code>可以理解为展示层和应用层的结合，七层的负载均衡我们使用的是Nginx。在该层中我们进行应用逻辑的协调和拼装，这层绝大部分REST服务是无状态的，对于需要上下文的逻辑，会话也维持在这层，这一层非常薄。<code>核心逻辑层</code>维护着业务逻辑和业务对象的状态，该层是领域的体现，也是服务治理的核心层。这层的复杂度一般是最高的，因为领域建模、数据持久化、业务流转等都在此完成。为了降低复杂度提高可维护性，这层的项目要严格进行分层架构。<code>基础服务层</code>比较简单，就是一些常规的基础服务，例如:短信网关、邮件网关、全局唯一ID生成器、Pinpoint、推送服务等等。</p><p>为了尽可能让发布简单，不论是WEB服务还是Dubbo服务，我们都使用<a href="https://wrapper.tanukisoftware.com/doc/english/download.jsp" target="_blank" rel="noopener">JSW</a>打包成Zip包来发布，对应的maven插件为<code>appassembler-maven-plugin</code>。WEB服务我们基于Spring Boot，通过内嵌Tomcat来实现Zip的创建。配置中心我们选择了<a href="https://github.com/knightliao/disconf" target="_blank" rel="noopener">Disconf</a>，不但让代码和配置分离，并且让我们一个包能够运行在多套环境。使用Disconf得注意配置文件的加载顺序，特别是在Spring Boot项目中，WEB配置优先于Disconf相关Bean加载，可以使用<code>@PropertySource</code>通过URL来引入Disconf的配置。<a href="http://swagger.io/" target="_blank" rel="noopener">Swagger</a>用来生成REST接口文档，代码即文档，让接口的文档维护和测试更加容易。随着开源组件的引入越来越多，要迅速搭建一套系统越来越不容易，所以我们抽象出了多种类型的Archetype，各种基础代码和配置都全部自动生成，降低基础组件的使用的成本。在核心库的版本管理上，我们通过maven的parent来管理，从而加强约束将冲突降低最低，也为统一的版本升级提供了便利。在服务降级和熔断方面，我们选择了Netflix OSS中的<a href="https://github.com/Netflix/Hystrix" target="_blank" rel="noopener">Hystrix</a>，不过在我们还是使用Spring Cloud Netflix来集成，不过这个还没有大规模推广。</p><p>读写分离作为最基础的需求，我们没有选择Proxy，而是在Mybatis和Spring的基础上做了二次封装。实现的原理就是事务内的所有SQL语句和没有事务的insert、update、delete走主库，select走从库，但如果select要强制路由到主库，可以在SQL语句头上加/*master*/来实现路由。通过重写Spring的AbstractRoutingDataSource类来实现动态数据源，在事务管理上通过重写Spring的DataSourceTransactionManager来实现事务对数据源的选择，然后再动态代理Mybatis的SqlSession在InvocationHandler的实现中进行路由，如果发现事务已经开启就直接走主库，如果没有事务再进行SQL语句解析从而来选择主从库。网上也有大量的实现，不过都比较粗糙，一定要大量测试，并且弄明白里面所有的门门道道。在分库分表方面，我们选择了<a href="https://github.com/dangdangdotcom/sharding-jdbc" target="_blank" rel="noopener">sharding-jdbc</a>，不过目前还没有用到核心的业务逻辑中，在Dragon的研发时，抱着测试该库的目的，没有使用HBase，选择使用它进行了分库分表，目前使用下来还是不错。当涉及到分布式事务时，我们使用Spring的best efforts 1pc来尽量保证数据的一致性。全局ID生成器的用途广泛，特别是在分库分表业务中，我们选用了twitter的<a href="https://blog.twitter.com/2010/announcing-snowflake" target="_blank" rel="noopener">snowflake</a>。</p><p>缓存和分布式Session是每一个可以水平扩展系统都不可能避免的需求，Spring作为技术方案的基石，Spring的Cache和Session自然也作为了我们的首选方案。虽然Spring作为Pivital的看家项目，但在使用中也给我们带来了一些麻烦。Session需要使用Redis的命令，已经上线运行的Twemproxy不支持，所以我们只好切换到Redis的sentinel集群。Session由于发布不久，功能不是很强大，所以我们也做了二次扩展，并且修复了超时设置的小bug。虽然使用起来并不是很完美，但是基于对Pivital的信任，我们还是坚定紧跟Spring的发展。</p><p>在基础设施选型和建设上，不论是MHA的Mysql、FastDFS+GraphicsMagick、RabbitMQ的Mirror Queue、Sentinel的Redis还是大数据相关的Hadoop、Hbase都是很成熟的解决方案，我们的主要精力都放在了运维方面。对于基础设施，我们的原则是尽可能少，因为运维成本很高，要运维好更难。</p><p>安全放在最后讲，并不是因为安全不重要，而是大家都知道很重要，要做好却很难，特别是红星悦家这种刚起步的公司。在内部没有安全团队，并且安全意识比较薄弱的情况下，我们通过内部培训和外部合作来共同提高系统安全。过去一年，我们实现了全站的HTTPS，在账号体系的设计上为了简化动态盐和慢哈希的使用，我们使用了Spring-security中的BCryptPasswordEncoder。XSS防御使用OWASP提供的<a href="https://www.owasp.org/index.php/ESAPI" target="_blank" rel="noopener">ESAPI</a>提供的函数进行编码。在CSRF方面，一是遵循Restful风格，二是通过一次性Token和Refer检验；SQL注入防御上就只使用了PreparedStatement，避免了绝大部分攻击。不过在业务驱动的重压之下，虽然安全很多时候被放到了最后一位，不论是内部的理念灌输还是外部渗透测试，我们都一直在努力。</p><h4 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h4><p>一家之言，不登大雅之堂，仅供大家参考。悦家作为刚起步一年的初创公司，虽然技术架构上有了一个简单的雏形并且没有经历过大流量的考验，但是从无到有从粗放到精细的架构和技术变迁正是技术的最大乐趣所在，所以我们也诚邀有志之士共建红星悦家的技术体系，特别是精通容器/服务编排相关技术的大牛。非知之艰，行之惟艰，与君共勉！</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;这篇文章最早发布在&lt;a href=&quot;http://mp.weixin.qq.com/s?__biz=MzA5Nzc4OTA1Mw==&amp;amp;mid=2659599087&amp;amp;idx=1&amp;amp;sn=b113e5fbfccd8145a61f026250e08081&amp;amp;chksm=8be997fdbc9e1eebaba52b4b2a0606acfb50f78ee5267d3b8912866039960f31f57438bfbd54&amp;amp;mpshare=1&amp;amp;scene=5&amp;amp;srcid=0324MRCZlO2zYn4Ly3TZhr4D#rd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;聊聊架构&lt;/a&gt;微信公众号，结果引发了大家的众多评论，有对红星所做事情的疑惑也有对技术合理性的质疑。不论哪种情况，很高兴抛出了一个让大家还有兴趣聊两句的话题，还是那句话不求共鸣，只求博君一笑，不论是嘲笑还是微笑！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;互联网和移动互联网的飞速崛起，对传统行业造成了破坏性的冲击和几乎窒息的压迫，迫使他们必须做出改变和应对。在这个都想拿到船票的时刻，技术的重要性已经可以媲美商业模式，因为再好的商业模式都离不开技术来落地，所以谁的技术领先，谁就有可能最快抵达终点。但是众所周知，传统行业的IT部都是支撑部门，在信息化建设的研发能力上是先天不足的。红星美凯龙是一家超过30年的老牌传统企业，虽然过去几年在信息化的建设和互联网的探索上投入了大量的资本，但是在技术方面的积淀收效甚微。随着红星的港股上市，再次开启互联网战略，成立红星悦家互联网集团，我们在吸收了过去经验教训的基础上，重新架构红星的技术体系。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="架构" scheme="https://www.yangguo.info/tags/%E6%9E%B6%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>分布式系统研发心得</title>
    <link href="https://www.yangguo.info/2017/01/25/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E7%A0%94%E5%8F%91%E5%BF%83%E5%BE%97/"/>
    <id>https://www.yangguo.info/2017/01/25/分布式系统研发心得/</id>
    <published>2017-01-25T05:05:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>随着SOA、微服务等诸多概念的炒作，再加上Dubbo、Spring Cloud、Netflix OSS等诸多开源软件的支撑，要构建一套大型的分布式系统的技术门槛已经变得非常之低。但是这并不意味着构建一套大型分布式系统就是很容易的，因为任何系统都是领域知识的体现。所以笔者将这些年系统研发过程中的一点感悟记录下来，仅供参考。</p></blockquote><hr><h2 id="统一内部通用语言"><a href="#统一内部通用语言" class="headerlink" title="统一内部通用语言"></a>统一内部通用语言</h2><p>Question:</p><ol><li><p><code>SPU=SKU</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">A:把类目X下的SPU给我。</span><br><span class="line">B:把类目X下的SKU给了A。</span><br></pre></td></tr></table></figure></li><li><p><code>ID=CODE</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">A:我给你商场ID，你给我商场信息。</span><br><span class="line">B:A给我商场CODE，我用商场CODE去查询。</span><br></pre></td></tr></table></figure></li></ol><p>为了解决这个问题，所有的团队成员都应该意识到需要创建一种通用的语言，并且时常提醒大家对基本的内容保持专注，在任何需要的时候都使用这种语言。我们应该尽量少地在这些场合使用我们自己的行话，应该使用通用语言，因为它能帮助我们更清晰、更精确地交流。</p><a id="more"></a><p>通用语言连接起设计中的所有的部分，为设计团队今后顺利开展工作建立了前提。 完成一个大规模项目的设计可能需要花费数周乃至数月的时间。团队成员会发现一些初始的概念是不正确的或者不合时宜，或者发现一些需要考虑并放进总体设计中的新的设计元素。没有一种公共的语言，所有的这一切都是不可能的。</p><p>这种语言的形成可不是一日之功，需要开展艰难的工作，重点在于确保发现语言的那些关键元素。我们需要发现定义领域和模型的那些关键概念，发现描述它们的适当用词，并开始使用它们。它们当中的一些概念可能很容易被发现，但另一些则不然。</p><p>通用语言的重要性，不言而喻，但是我们很多时候却并没有把它提升到一个非常重要的高度，这样导致了内部的沟通效率非常低下。在各种场合、组织内部强化约束这些通用语言的抽象和使用，它并不会立马提升效率，但是你得坚信这样做是值得的。</p><h2 id="模型驱动设计（MDD）"><a href="#模型驱动设计（MDD）" class="headerlink" title="模型驱动设计（MDD）"></a>模型驱动设计（MDD）</h2><p>产品经理（软件分析人员）和业务领域专家（老板 or 甲方）在一起工作了一段时间，一起找出来了领域的基础元素，理清了元素之间的关系，创建了一个正确的模型，这个模型的确正确捕获了领域知识。然后模型交给软件工程师。开发人员看模型时可能会发现模型中的有些概念或者关系无法使用代码来正确地表达。所以他们使用模型作为灵感的源泉，创建了自己的设计，虽然其设计借鉴了模型的某些思想，但是他们还增加了一些自己的东西。开发过程继续进行，更多的类被添加到代码中，进一步加大了原始模型和最终实现的差距。在这种情况下，很难保证产生好的结果。优秀的开发人员可能会做出一个能够工作的产品，但它能经得起时间的考验吗?它能被很容易地扩展吗?它能被容易地维护吗?当然还有更差的就是设计反推需求，工程师在设计系统的时候，发现流程走不下去，需要提新的需求，而这些需求软件分析人员根本就没有考虑到。这些问题从软件诞生以来就存在，只是在大型分布式系统被成倍的放大。</p><p>如果开发人员能够参加需求讨论会议，并在开始做编码设计之前对领域 知识和模型获得一个清晰完整的视图，他们后面的设计工作将会更有效率。最好的方法是将领域建模和系统设计紧密关联起来，领域模型在构建时就考虑到软件实现和设计，如果有问题及时调整和修正方向。这样就能获得一个有效的领域模型，程序员就能非常方便的将这种模型转化为代码。当然并不是这样做就能够保证沟通是没有任何障碍的，很多软件分析人员（产品经理）总以为程序员在刁难他们，因为他们不能理解建模中的难点和技术问题，这也是我坚持认为一个好的软件分析人员一定要写过几年代码。在我们目前的职业生涯中遇见过好几个工程师出身的产品经理，我经常拿他们跟非工程师出身的比较，不论跟程序员的讨论实现细节还是领域模型的抽象，他们的确具有一定优势。</p><p>当我们创建一个软件应用时，这个应用的很大一部分并没有直接与领域关联，但它们是基础设施的一部分或者是为软件本身提供服务的。最好能让应用中的领域部分与其余部分相比保持尽可能小(而不是和其余部分掺杂在一起)，因为一个典型的应用包含了大量访问数据库、访问文件或网络、用户界面等相关的代码。</p><p>因此，将一个复杂的程序划分成多个层。为每一个层开发一个内聚的设计，让每个层仅依赖于它底下的那些层。遵照标准的架构模式实现与其上面的那些层的低耦合。将领域模型相关的代码集中到一个层中，把它从用户界面、应用和基础设施代码中隔离开来。领域对象不必再承担显示自己、保存自己、管理应用任务的职责，而是专注于表达领域模型。这会让一个模型逐渐进化得足够丰满、足够清晰，以便捕获基本的业务知识， 并且能够正常工作。</p><p>常用的分层架构方案如下：</p><table><thead><tr><th style="text-align:center">层</th><th style="text-align:center">说明</th></tr></thead><tbody><tr><td style="text-align:center">展示层</td><td style="text-align:center">向用户展示软件的功能和信息</td></tr><tr><td style="text-align:center">应用层</td><td style="text-align:center">应用的逻辑的协调，不包含业务逻辑和业务对象的状态，包含会话的上下文</td></tr><tr><td style="text-align:center">领域层</td><td style="text-align:center">核心业务逻辑，业务对象状态保持，包含完整的领域信息</td></tr><tr><td style="text-align:center">基础层</td><td style="text-align:center">实体对象的持久化，公用库，基础服务</td></tr></tbody></table><p>分层就必然面临着边界的划分问题，因为对于一个大型的分布式应用，每一层都是极其庞杂的，所以是先按领域划分模块然后再分层。各个模块清晰的被划分出来，各层被清晰的定义出来，这样就可以有效的避免代码混乱和降低管理成本。因此，将一个复杂的程序划分成多个层。为每一个层开发一个内聚的设计，让每个层仅依赖于它底下的那些层。遵照标准的架构模式实现与其上面的那些层的低耦合。将领域模型相关的代码集中到一个层中，把它从用户界面、应用和基础代码中隔离开来。领域对象不必再承担显示自己、保存自己、管理应用任务的职责，而是专注于表达领域模型。这会让一个模型逐渐进化得足够丰满、足够清晰，以便捕获基本的业务知识，并且能够正常工作。因此，将一个复杂的程序划分成多个层。为每一个层开发一个内聚的设计，让每个层仅依赖于它底下的那些层。遵照标准的架构模式实现与其上面的那些层的低耦合。将领域模型相关的代码集中到一个层中，把它从用户界面、应用和基础设施代码中隔离开来。领域对象不必再承担显示自己、保存自己、管理应用任务的职责，而是专注于表达领域模型。这会让一个模型逐渐进化得足够丰满、足够清晰，以便捕获基本的业务知识，并且能够正常工作。当实体、值对象、服务被清晰无误的定义出来，领域知识的落地就基本完成。</p><p>虽然内聚始于类和方法，它也可以用在模块级别。模块内聚一般分为两种，通信性内聚和功能性内聚，把操作相同数据的服务放在一个模块叫做通信性内聚，把具有强关联性的业务逻辑放在一个模块叫功能性内聚，功能性内聚被认为是最佳实践。不论如何聚合，我们还是会看到很多对象会跟其他的对象发生关联，形成了一个复杂的关系网，不论是一对一、一对多还是多对多。来自模型的挑战常常不是让它们尽量完整，而是让它们尽量地简单和容易理解。这意味着，除非直到模型中嵌入了对领域的深层理解，否则大多数时候需要对模型中的关系进行消减和简化。 首先，要删除模型中非基本的关联关系。它们可能在领域中是存在的，但它们在我们的模型中不是必要的，所以我们要删除它们。其次，可以通过添加约束的方式来减少多重性。如果很多对象满足一种关系，那么在这个关系上加入正确的约束之后，很有可能只有一个对象会继续满足这种关系。第三，很多时候双向关联可以被转换成非双向的关联。</p><h2 id="接口设计原则"><a href="#接口设计原则" class="headerlink" title="接口设计原则"></a>接口设计原则</h2><ol><li>尽可能保证接口幂等性，不论是读还是写。读不需要多解释，写的并发问题，所以乐观锁等不错的思路。</li><li>接口要尽可能可以降级</li><li>需求确定之后，接口和数据模型先行，协议和字段需要在Java Doc和Swagger API Docs中详细描述</li><li>领域层的接口先从一个比较大的服务边界开始，然后随着时间推移基于业务需求来重构成更小的。我们应该关注微服务的范围，而不是一味的把服务做小。一个服务的（正确的）大小应该等于满足某个特定业务能力所需要的大小。他们应该是内聚而完整的。</li><li>接口需要向下兼容，所以接口都需要带上版本号，如果底层的数据结构发生了颠覆性变化，要充分考虑老版本接口问题，特别是C端产品。</li><li>接口要尽量保证无状态，如果的确有状态，可以按状态划分为多个接口。</li><li>不能过度抽象接口，例如public Map query(Map map)这类接口，维护和使用都非常困难。但是也不能不抽象，否者接口数会暴增，随着业务的稳定，有必要抽象合并。</li></ol>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;随着SOA、微服务等诸多概念的炒作，再加上Dubbo、Spring Cloud、Netflix OSS等诸多开源软件的支撑，要构建一套大型的分布式系统的技术门槛已经变得非常之低。但是这并不意味着构建一套大型分布式系统就是很容易的，因为任何系统都是领域知识的体现。所以笔者将这些年系统研发过程中的一点感悟记录下来，仅供参考。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;统一内部通用语言&quot;&gt;&lt;a href=&quot;#统一内部通用语言&quot; class=&quot;headerlink&quot; title=&quot;统一内部通用语言&quot;&gt;&lt;/a&gt;统一内部通用语言&lt;/h2&gt;&lt;p&gt;Question:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SPU=SKU&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;A:把类目X下的SPU给我。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;B:把类目X下的SKU给了A。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ID=CODE&lt;/code&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;A:我给你商场ID，你给我商场信息。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;B:A给我商场CODE，我用商场CODE去查询。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决这个问题，所有的团队成员都应该意识到需要创建一种通用的语言，并且时常提醒大家对基本的内容保持专注，在任何需要的时候都使用这种语言。我们应该尽量少地在这些场合使用我们自己的行话，应该使用通用语言，因为它能帮助我们更清晰、更精确地交流。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="管理、架构、DDD" scheme="https://www.yangguo.info/tags/%E7%AE%A1%E7%90%86%E3%80%81%E6%9E%B6%E6%9E%84%E3%80%81DDD/"/>
    
  </entry>
  
  <entry>
    <title>TOP100案例分享</title>
    <link href="https://www.yangguo.info/2016/12/15/%E4%BC%A0%E7%BB%9F%E4%BC%81%E4%B8%9A%E4%BA%92%E8%81%94%E7%BD%91%E8%BD%AC%E5%9E%8B%E7%9A%84%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93/"/>
    <id>https://www.yangguo.info/2016/12/15/传统企业互联网转型的架构之道/</id>
    <published>2016-12-15T09:54:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>受MSUP TOP100组委会的邀请，12月12号在国际会议中心分享了《传统互联网转型的架构之道》，会后也和一些业界的朋友做了更深层的探讨，收货很多。下面是我分享内容的PPT，供感兴趣的朋友参考。由于图片经过压缩之后不是很清晰，如果阅读清晰版，可以下载<a href="/images/top100.pdf">PDF</a>查看。</p></blockquote><hr><p><img src="/images/top100/幻灯片01.jpg" alt="幻灯片01.jpg"></p><a id="more"></a><p><img src="/images/top100/幻灯片02.jpg" alt="幻灯片02.jpg"></p><p><img src="/images/top100/幻灯片03.jpg" alt="幻灯片03.jpg"></p><p><img src="/images/top100/幻灯片04.jpg" alt="幻灯片04.jpg"></p><p><img src="/images/top100/幻灯片05.jpg" alt="幻灯片05.jpg"></p><p><img src="/images/top100/幻灯片06.jpg" alt="幻灯片06.jpg"></p><p><img src="/images/top100/幻灯片07.jpg" alt="幻灯片07.jpg"></p><p><img src="/images/top100/幻灯片08.jpg" alt="幻灯片08.jpg"></p><p><img src="/images/top100/幻灯片09.jpg" alt="幻灯片09.jpg"></p><p><img src="/images/top100/幻灯片10.jpg" alt="幻灯片10.jpg"></p><p><img src="/images/top100/幻灯片11.jpg" alt="幻灯片11.jpg"></p><p><img src="/images/top100/幻灯片12.jpg" alt="幻灯片12.jpg"></p><p><img src="/images/top100/幻灯片13.jpg" alt="幻灯片13.jpg"></p><p><img src="/images/top100/幻灯片14.jpg" alt="幻灯片14.jpg"></p><p><img src="/images/top100/幻灯片15.jpg" alt="幻灯片15.jpg"></p><p><img src="/images/top100/幻灯片16.jpg" alt="幻灯片16.jpg"></p><p><img src="/images/top100/幻灯片17.jpg" alt="幻灯片17.jpg"></p><p><img src="/images/top100/幻灯片18.jpg" alt="幻灯片18.jpg"></p><p><img src="/images/top100/幻灯片19.jpg" alt="幻灯片19.jpg"></p><p><img src="/images/top100/幻灯片20.jpg" alt="幻灯片20.jpg"></p><p><img src="/images/top100/幻灯片21.jpg" alt="幻灯片21.jpg"></p><p><img src="/images/top100/幻灯片22.jpg" alt="幻灯片22.jpg"></p><p><img src="/images/top100/幻灯片23.jpg" alt="幻灯片23.jpg"></p><p><img src="/images/top100/幻灯片24.jpg" alt="幻灯片24.jpg"></p><p><img src="/images/top100/幻灯片25.jpg" alt="幻灯片25.jpg"></p><p><img src="/images/top100/幻灯片26.jpg" alt="幻灯片26.jpg"></p><p><img src="/images/top100/幻灯片27.jpg" alt="幻灯片27.jpg"></p><p><img src="/images/top100/幻灯片28.jpg" alt="幻灯片28.jpg"></p><p><img src="/images/top100/幻灯片29.jpg" alt="幻灯片29.jpg"></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;受MSUP TOP100组委会的邀请，12月12号在国际会议中心分享了《传统互联网转型的架构之道》，会后也和一些业界的朋友做了更深层的探讨，收货很多。下面是我分享内容的PPT，供感兴趣的朋友参考。由于图片经过压缩之后不是很清晰，如果阅读清晰版，可以下载&lt;a href=&quot;/images/top100.pdf&quot;&gt;PDF&lt;/a&gt;查看。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;/images/top100/幻灯片01.jpg&quot; alt=&quot;幻灯片01.jpg&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="管理、架构" scheme="https://www.yangguo.info/tags/%E7%AE%A1%E7%90%86%E3%80%81%E6%9E%B6%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>APM技术总结</title>
    <link href="https://www.yangguo.info/2016/12/04/APM/"/>
    <id>https://www.yangguo.info/2016/12/04/APM/</id>
    <published>2016-12-04T06:00:00.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<p>APM(Application Performance Management)经过2-3年的发展早已经被大家所熟悉了，当Google的Dapper刚发表的时候，就有一大批的公司和组织进行了产品化，大家熟悉的淘宝的鹰眼、点评的Cat、OneAPM等都是这方面的先行者。随着SOA和微服务的普及，越来越多的公司采用了这样的分布式架构，因此对APM的需求更是越来越强烈。本篇文章首先会简单阐述下Dapper的思想，然后会简单阐述下笔者自研的Dragon和放弃它的原因，最后阐述下Pinpoint架构。这篇文章主要的目的，一是记录研发的心路历程，二是希望为那些未做但想做的同行提供一点经验教训。</p><a id="more"></a><h3 id="Dapper"><a href="#Dapper" class="headerlink" title="Dapper"></a>Dapper</h3><p><strong>分布式系统</strong> 演变到现在，用户的一次点击事件，导致后端几十次甚至上百次的同步或异步调用，这已经不再仅仅是Google、阿里、Facebook等级别的公司才会出现的情况。随着用户对产品的体验要求越来要高，任何的延迟或者抖动都会导致用户的流失，这对于任何的商业公司来说都是不能够被接收的现实。所以监控并且是7x24小时的不间断监控，是互联网企业的标配。Dapper的核心在于Call Tree模型，它抽象出了Trace、Span、Annotation，用简单的3个模型就将整个调用串起来了。Dapper的思想是非常简单明了的，但是Dapper却又提出了低损耗、应用透明、可扩展。个人觉得应用透明应该是非常难做的一件事情，不同的程序语言、不同的RPC框架、不同的线程调用模型，都是必须考虑的因素，当然采样率的控制、跟踪数据的收集存储、带外数据的携带、安全和隐私等等也都不是容易的事情。Dapper虽然代码没有开源，但是它的思想却为我们提供了非常高的起点，所以才有APM市场百花齐放的景象。</p><hr><h3 id="Dragon"><a href="#Dragon" class="headerlink" title="Dragon"></a>Dragon</h3><p><strong><a href="https://github.com/chengdedeng/dragon" target="_blank" rel="noopener">Dragon</a></strong> 是我读完Dapper简单看了下Cat源码之后，针对使用Dubbo作为RPC的系统研发的一套调用链追踪系统。首先我不是一个非常喜欢造轮子的人，一是因为在当时没有针对Dubbo的调用链追踪工具，点评的Cat是基于点评内部的RPC框架Pigeon来研发的，二是Pinpoint我当时觉得是韩国人开发的，文档是一个问题，所以打算自己写了一套。当然这套东西最后被我放弃了，其实原因很简单因为我发现Pinpoint已经做的非常不错了，虽然文档还是比较烂，不过好在代码还比较靠谱，不懂的直接看代码，并且Dubbo的扩展也已经支持。之前凭借个人的判断，没有去仔细了解这个项目导致自己花了很多精力自研，如果当时就基于Pinpoint去实现Dubbo的扩展点就更完美了。关于自研和开源方案，笔者经常犯难，也做过很多错误的决定，但是任何事情都具有两面性，所以我能告诉你的就是大胆去做，及时改正。</p><p>虽然Dragon被我放弃了，但是不代表这是一个失败的项目或者说没有可取之处，正式由于Dragon的研发让我明白Pinpoint中的很多闪光之处和不足之处。Dragon在公司稳定运行了大概半年之久，发现和解决了不少的问题。Dragon的架构非常简单，实现了Dubbo Filter的扩展点，做了一个轻量级的AOP。Trace Id、Span Id都作为Dubbo Protocol的Attachment参数携带给RPC的下一跳。本跳的Trace数据被异步发送给Collector，Collector首先发送到消息队列，然后再入库。存储我选择了Mysql，当然是经过分库分表的，同时每晚有定时任务删除过期的数据保证数据量不至于太大。Web端根据时间和接口名去查询数据，最后绘制成调用链。当然异常数据会作为带外数据被收集起来，这个地方还可以作为插件的形式收集别的带外数据。最后如果用户发现一条Trace链路很慢，想看跨系统的所有的日志，只要将TraceId输入到我们kibana中就可以把所有的日志按时间序串起来。因为我修改了LOG4J的MDC，将TraceId放入到了线程的上下文中。为了形象的说明系统的样子，用图来讲比较方便说明。图中红色的都是表示有问题的，绿色表示正常。线上的时间表示网络耗时，左边的表示下行耗时，右边的表示上行耗时。每一个节点都可以点击，进去之后有该节点的地址和调用总耗时。</p><p><img src="/images/dragon1.png" alt="dragon1"></p><p><img src="/images/dragon2.png" alt="dragon2"></p><p><img src="/images/dragon3.png" alt="dragon3"></p><p><img src="/images/dragon4.png" alt="dragon4"></p><p><img src="/images/dragon5.png" alt="dragon5"></p><p>有人可能看完这个之后，可能会问你这个只能查询微观粒度的，我怎么从宏观的角度去判断我的系统在这段时间不是很稳定或者慢了。其实我们还有一套Metrics系统，它是从宏观层面来反应我们所有系统的状态，如果它反应有问题并报警，再结合Dragon来定位就能够解决问题。</p><hr><h3 id="Pinpoint"><a href="#Pinpoint" class="headerlink" title="Pinpoint"></a>Pinpoint</h3><p><a href="https://github.com/naver/pinpoint" target="_blank" rel="noopener">Pinpoint</a>通过Java Agent技术通过Attachment的方式进行了代码的织入，其实这并不是多高深的技术，可以详见我的文章《<a href="http://www.yangguo.info/2015/07/18/JavaAgent%E7%9A%84%E4%B8%80%E7%82%B9%E6%80%9D%E8%B7%AF/">JavaAgent的一点思路</a>》。Pinpoint的架构和设计思路跟Dragon很像，不过它采用了Nosql的Hbase作为存储，数据使用TCP/UDP进行传送，并且可以通过Web UI实时获取Agent中的数据。它的架构见下图，不过稍微有点老了，暂且看看吧：</p><p><img src="/images/pinpoint1.png" alt="pinpoint1"></p><p>对于监控数据或者非业务数据，我个人是非常喜欢使用UDP的，因为服务哪怕crash了，也不会对业务造成任何影响。虽然以上的功能已经让它看上去非常性感了，不过真正吸引我的却是它插件式的设计，可以根据自己的需求来研发任意插件。Pinpoint虽然有这样那样的优点，不过每个使用者都会觉得自己的一些需求还是没有满足，例如通过TraceId将所有系统的日志串起来，这就需要我们去修改部分源码。下面几张图，能够形象的展示Pinpoint的一些特色。</p><p><img src="/images/pinpoint2.png" alt="pinpoint2"></p><p><img src="/images/pinpoint3.png" alt="pinpoint3"></p><p><img src="/images/pinpoint4.png" alt="pinpoint4"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;APM(Application Performance Management)经过2-3年的发展早已经被大家所熟悉了，当Google的Dapper刚发表的时候，就有一大批的公司和组织进行了产品化，大家熟悉的淘宝的鹰眼、点评的Cat、OneAPM等都是这方面的先行者。随着SOA和微服务的普及，越来越多的公司采用了这样的分布式架构，因此对APM的需求更是越来越强烈。本篇文章首先会简单阐述下Dapper的思想，然后会简单阐述下笔者自研的Dragon和放弃它的原因，最后阐述下Pinpoint架构。这篇文章主要的目的，一是记录研发的心路历程，二是希望为那些未做但想做的同行提供一点经验教训。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="APM" scheme="https://www.yangguo.info/tags/APM/"/>
    
      <category term="Pinpoint" scheme="https://www.yangguo.info/tags/Pinpoint/"/>
    
      <category term="Dapper" scheme="https://www.yangguo.info/tags/Dapper/"/>
    
  </entry>
  
  <entry>
    <title>分布式事务笔记</title>
    <link href="https://www.yangguo.info/2016/05/23/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%AC%94%E8%AE%B0/"/>
    <id>https://www.yangguo.info/2016/05/23/分布式事务笔记/</id>
    <published>2016-05-23T07:52:29.000Z</published>
    <updated>2020-06-18T14:51:19.004Z</updated>
    
    <content type="html"><![CDATA[<p>最近研究避免分布式事务，有幸读到<a href="https://twitter.com/david_syer" target="_blank" rel="noopener">David Syer</a>于09年发布在JavaWorld的<a href="http://www.javaworld.com/article/2077963/open-source-tools/distributed-transactions-in-spring--with-and-without-xa.html" target="_blank" rel="noopener">HOW-TO<br>Distributed transactions in Spring, with and without XA</a>，虽然文章和测试代码都老了点，但是思想不变。</p><h3 id="Full-XA-with-2PC"><a href="#Full-XA-with-2PC" class="headerlink" title="Full XA with 2PC"></a>Full XA with 2PC</h3><p>如果需要近乎完美的防护，2PC绝对是不二之选，但是它的性能损耗是显而易见的，这也是为什么大家谈分布式事务而闻之色变的原因。此处我们不谈性能，只谈Spring是如何支持2PC XA的。Spring为我们提供了JtaTransactionManager和声明式事务，将复杂的同步细节进行了托管。我们只需要关注DataSource的配置，例如通过开源的JTA实现<a href="https://www.atomikos.com/" target="_blank" rel="noopener">Atomikos</a>来配置DataSource。</p><h3 id="XA-with-1PC-Optimization"><a href="#XA-with-1PC-Optimization" class="headerlink" title="XA with 1PC Optimization"></a>XA with 1PC Optimization</h3><p>1PC可以看做是单一事务资源下2PC的的优化版本，许多的事务管理器通过这种模式来优化2PC的过度开销。但是并不是单一事务资源，就表示一定走的是1PC，因为XA事务管理器会根据通讯时间的长度，来判断网络通讯的危险期，即使连接一个数据库也应该使用两阶段提交，如果通讯危险期很短，多个事务资源也有可能使用一阶段提交。</p><a id="more"></a><h3 id="XA-and-the-Last-Resource-Gambit"><a href="#XA-and-the-Last-Resource-Gambit" class="headerlink" title="XA and the Last Resource Gambit"></a>XA and the Last Resource Gambit</h3><p>目前市面上绝大多数的XA事务管理都有一个特点，那就是不论资源是部分XA兼容还是所有的都XA兼容，事务管理器都能够提供相同的事务保障。当资源经过排序，并且非XA资源通过投票机制，可以保证事务失败时所有的资源都能回滚。它对事务的保证是可以完全放心的，但是如果事务失败不会留下太多信息，如果想获取这些信息，就需要一些高级实现了。</p><h3 id="XA模式总结"><a href="#XA模式总结" class="headerlink" title="XA模式总结"></a>XA模式总结</h3><p>前面介绍了三种基于XA协议的模式，不过从性能的方面来考虑，这些模式可能不能让我们满意，尤其是特别关注并发的互联网业务。我们可以了解并学习它，知道何时、如何避免使用XA，何时又必须使用。当然也有开发能力很强的团队，自己研发了两阶段提交事务框架，例如支付宝。</p><h3 id="Shared-Transaction-Resource-pattern"><a href="#Shared-Transaction-Resource-pattern" class="headerlink" title="Shared Transaction Resource pattern"></a>Shared Transaction Resource pattern</h3><p>这个模式就是将所有的事务资源绑定到一个相同的资源，从而实现分布式事务，提升系统吞吐量。该模式的使用场景是受限制的，不能拿来处理所有的用例，但是它如XA一样坚固可靠。为了详细说明该模式，我通过两个案例来阐述。</p><h4 id="共享数据库Connection"><a href="#共享数据库Connection" class="headerlink" title="共享数据库Connection"></a>共享数据库Connection</h4><p>我们在使用spring的声明式事务的时候，通常都是由service层来控制的，这点是基础共识。这样不论我们是使用ORM还是JDBC抽象框架，只要底层的Connection是共享的，那么我们即使配置了多个上层的模板(Mybatis or JdbcTemplate)，事务的安全就能够得到保证。这便是共享资源模式最简单最直接的体现，只是我们平时没有关注而已。</p><h4 id="消息驱动的单Database更新"><a href="#消息驱动的单Database更新" class="headerlink" title="消息驱动的单Database更新"></a>消息驱动的单Database更新</h4><p>这种模式就是将消息中间件的Connection和数据库的Connection交给一个代理，这个代理依赖于消息中间厂商的存储策略细节，并且不是所有的供应商都提供这种模式。这种模式的使用并不常见，因为我们更喜欢用<code>Best Efforts 1PC pattern</code>中消息驱动模式，因为它更简单。下面的配置可以让我们窥一斑而见全豹，如果想更清晰的了解消息驱动的单Database的更新，请下载原文的代码研究。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"brokerService"</span> <span class="attr">class</span>=<span class="string">"org.apache.activemq.broker.BrokerService"</span></span></span><br><span class="line"><span class="tag"><span class="attr">init-method</span>=<span class="string">"start"</span> <span class="attr">destroy-method</span>=<span class="string">"stop"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"brokerName"</span> <span class="attr">value</span>=<span class="string">"broker"</span> /&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- Enable this if you need to inspect with JMX --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"useJmx"</span> <span class="attr">value</span>=<span class="string">"false"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"transportConnectorURIs"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">list</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">value</span>&gt;</span>vm://localhost<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">list</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"persistenceAdapter"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"org.apache.activemq.store.jdbc.JDBCPersistenceAdapter"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"dataSource"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"com.springsource.open.jms.JmsTransactionAwareDataSourceProxy"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"targetDataSource"</span> <span class="attr">ref</span>=<span class="string">"dataSource"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"jmsTemplate"</span> <span class="attr">ref</span>=<span class="string">"jmsTemplate"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"createTablesOnStartup"</span> <span class="attr">value</span>=<span class="string">"true"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Best-Efforts-1PC-pattern"><a href="#Best-Efforts-1PC-pattern" class="headerlink" title="Best Efforts 1PC pattern"></a>Best Efforts 1PC pattern</h3><p>该模式应用相当广泛，这也是我最喜欢的模式之一。虽然在安全性上不如XA，但是在大数据、高并发等性能上却得到了极大的提升。那我们如何判断我们的系统是否可以使用该模式，建议参考下面的两点：</p><ol><li>各个事务资源基础组件出错概率非常小。</li><li>业务可以设计为幂等性。</li></ol><p>只要满足以上两点，事务发生错误的可能性就非常小了。为了简单的说明此模式，我结合Rabbitmq和Mysql来说明，先简单的来一段配置。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"jsonMessageConverter"</span></span></span><br><span class="line"><span class="tag">        <span class="attr">class</span>=<span class="string">"org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">rabbit:connection-factory</span> <span class="attr">id</span>=<span class="string">"rabbitConnectionFactory"</span></span></span><br><span class="line"><span class="tag">                             <span class="attr">addresses</span>=<span class="string">"10.11.25.222"</span></span></span><br><span class="line"><span class="tag">                             <span class="attr">username</span>=<span class="string">"yangguo"</span></span></span><br><span class="line"><span class="tag">                             <span class="attr">password</span>=<span class="string">"yangguo"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">rabbit:template</span> <span class="attr">id</span>=<span class="string">"rabbitTemplate"</span> <span class="attr">connection-factory</span>=<span class="string">"rabbitConnectionFactory"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">message-converter</span>=<span class="string">"jsonMessageConverter"</span> <span class="attr">channel-transacted</span>=<span class="string">"true"</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>再来两段Java代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@Transactional</span>(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">provierSuccess</span><span class="params">()</span> </span>&#123;</span><br><span class="line">     User user = <span class="keyword">new</span> User();</span><br><span class="line">     user.setUserName(<span class="string">"yangguo"</span>);</span><br><span class="line">     user.setPassword(<span class="string">"yangguo"</span>);</span><br><span class="line">     userMapper.insert(user);</span><br><span class="line"></span><br><span class="line">     rabbitTemplate.convertAndSend(<span class="string">"amq.topic"</span>, <span class="keyword">null</span>, user);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>上面代码处理的业务逻辑是将用户插入到用户表的同时向消息队列发一条消息，插入DB和发送消息到MQ是包在一个事务中。下面的代码则是消费消息，也是包在一个事务中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Transactional</span>(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">consumeSuccess</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    User user= (User) rabbitTemplate.receiveAndConvert(<span class="string">"test-queue"</span>);</span><br><span class="line">    friendMapper.update(user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码其实很简单，首先配置一个支持事务的RabbitTemplate，主要设置<code>channel-transacted=&quot;true&quot;</code>，当然Mysql DataSource和Mybatis的配置，我就省略了，然后剩下的事情就交给Spring的Transaction吧。不过原文是使用ActiveMQ来讲解的，不过思想都是一样的。当然作者在原文中还解释了一种链式事务管理器，其实上面的例子也是一种链式思路，链式事务管理器非常注重顺序性，因为回滚和业务顺序正好相反。现阶段的spring事务处理，默认就是一种链式思路，只要你在service上面加了@Transaction或者在XML里面配置了切面，那么处于切面中的方法中的分布式资源就会受到spring事务管理器的托管。这也是最大努力一次提交模式流行的真正原因，因为它就是Spring事务管理器的核心。当然如果我们使用消息队列的Topic模式，是可以绕过<code>消息驱动的单Database更新</code>的，因为我们只要能够实现最终一致性就OK了。</p><h3 id="Nontransactional-Access-pattern"><a href="#Nontransactional-Access-pattern" class="headerlink" title="Nontransactional Access pattern"></a>Nontransactional Access pattern</h3><p>这个模式是需要特殊的业务场景，理想情况是非事务或者本地事务的逻辑是边缘业务，该业务具备幂等性或者从逻辑的角度来说具备幂等性。当然该模式可以是分布式事务和非事务的组合，也可以全部都是非事务逻辑，还可以是分布式事务和本地事务的组合。这种模式需要细致的分析，只有对业务充分了解，才能确定哪些可以走非事务或者本地事务。当然业务出错，你可以使用补偿机制，不过请注意通用的事务补偿通常都很麻烦。</p><h3 id="HALF消息-本地事务表-消息回查"><a href="#HALF消息-本地事务表-消息回查" class="headerlink" title="HALF消息+本地事务表+消息回查"></a>HALF消息+本地事务表+消息回查</h3><p>这种模式并不是David Syer文章中所提到的模式，不过该模式我认为是最简单和安全的模式，不过需要中间件支持，该类型中间件的代表就是<a href="https://rocketmq.apache.org/" target="_blank" rel="noopener">RocketMQ</a>。为了简单阐述该模式的，我就直接拿RocketMQ的交互流程。</p><p><img src="/images/rocketmq_flow.png" alt="rocketmq_flow"></p><p>该模式在性能和易用性上都有非常好的表现，不过就是需要自己消息回查逻辑，这种模式也是我使用最多的模式。该模式通过本地事务表和消息中间件的状态管理，将复杂的分布式问题简单化，唯一牺牲的就是时效性，因为它只能保证最终一致性。</p><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="https://www.ibm.com/developerworks/cn/java/j-lo-spring-ts1/" target="_blank" rel="noopener">Spring 事务管理高级应用难点剖析: 第 1 部分</a></p><p><a href="http://www.ibm.com/developerworks/cn/java/j-lo-spring-ts2/" target="_blank" rel="noopener">Spring 事务管理高级应用难点剖析: 第 2 部分</a></p><p><a href="http://www.ibm.com/developerworks/cn/java/j-ts1.html" target="_blank" rel="noopener">事务策略: 了解事务陷阱</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近研究避免分布式事务，有幸读到&lt;a href=&quot;https://twitter.com/david_syer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;David Syer&lt;/a&gt;于09年发布在JavaWorld的&lt;a href=&quot;http://www.javaworld.com/article/2077963/open-source-tools/distributed-transactions-in-spring--with-and-without-xa.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HOW-TO&lt;br&gt;Distributed transactions in Spring, with and without XA&lt;/a&gt;，虽然文章和测试代码都老了点，但是思想不变。&lt;/p&gt;
&lt;h3 id=&quot;Full-XA-with-2PC&quot;&gt;&lt;a href=&quot;#Full-XA-with-2PC&quot; class=&quot;headerlink&quot; title=&quot;Full XA with 2PC&quot;&gt;&lt;/a&gt;Full XA with 2PC&lt;/h3&gt;&lt;p&gt;如果需要近乎完美的防护，2PC绝对是不二之选，但是它的性能损耗是显而易见的，这也是为什么大家谈分布式事务而闻之色变的原因。此处我们不谈性能，只谈Spring是如何支持2PC XA的。Spring为我们提供了JtaTransactionManager和声明式事务，将复杂的同步细节进行了托管。我们只需要关注DataSource的配置，例如通过开源的JTA实现&lt;a href=&quot;https://www.atomikos.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Atomikos&lt;/a&gt;来配置DataSource。&lt;/p&gt;
&lt;h3 id=&quot;XA-with-1PC-Optimization&quot;&gt;&lt;a href=&quot;#XA-with-1PC-Optimization&quot; class=&quot;headerlink&quot; title=&quot;XA with 1PC Optimization&quot;&gt;&lt;/a&gt;XA with 1PC Optimization&lt;/h3&gt;&lt;p&gt;1PC可以看做是单一事务资源下2PC的的优化版本，许多的事务管理器通过这种模式来优化2PC的过度开销。但是并不是单一事务资源，就表示一定走的是1PC，因为XA事务管理器会根据通讯时间的长度，来判断网络通讯的危险期，即使连接一个数据库也应该使用两阶段提交，如果通讯危险期很短，多个事务资源也有可能使用一阶段提交。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Spring" scheme="https://www.yangguo.info/tags/Spring/"/>
    
      <category term="事务" scheme="https://www.yangguo.info/tags/%E4%BA%8B%E5%8A%A1/"/>
    
      <category term="分布式事务" scheme="https://www.yangguo.info/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>开发人员应该知道的安全知识</title>
    <link href="https://www.yangguo.info/2016/03/12/%E5%BC%80%E5%8F%91%E4%BA%BA%E5%91%98%E5%BA%94%E8%AF%A5%E6%8E%8C%E6%8F%A1%E7%9A%84%E5%AE%89%E5%85%A8%E7%9F%A5%E8%AF%86/"/>
    <id>https://www.yangguo.info/2016/03/12/开发人员应该掌握的安全知识/</id>
    <published>2016-03-12T15:11:45.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近几年层出不穷的安全漏洞，再一次要求大家提高安全意识。安全是很大的一个话题，下面是我平时工作总结出来的一些安全方面的经验。</p></blockquote><hr><h3 id="关闭错误显示"><a href="#关闭错误显示" class="headerlink" title="关闭错误显示"></a>关闭错误显示</h3><p>很多网站不知道是运维人员的业余还是疏忽，网站的错误显示没有关闭。攻击者只需要通过调整参数，就可以获得错误的堆栈信息，从而推断出网站使用的相关技术栈，从而为后面的攻击大开方便之门。</p><a id="more"></a><hr><h3 id="避免SQL注入经验"><a href="#避免SQL注入经验" class="headerlink" title="避免SQL注入经验"></a>避免SQL注入经验</h3><p>关于SQL注入，只要是稍微有点经验的程序员都应该明白是怎么回事。此处不谈SQL注入的方法和原理，只谈我们在开发过程中怎样编程是较安全的方式，从而将注入的风险降到最低。</p><ol><li>前面已经说过了关闭错误显示，只要我们关闭了错误显示，我们就可以将盲注的难度提升。</li><li>参数校验和过滤特殊符号，这对喜欢拼SQL的人来说是一个可行的方案，不过成本很高。</li><li>绑定变量，使用预编译语句。例如：PreparedStatement（需要驱动支持），框架层面如Mybatis中要尽量使用<code>#</code>符号，少用和不用<code>$</code>，如果非要<code>$</code>使用也要用方案2来进行补偿。</li></ol><p>SQL注入已经经历10几个年头了，它的攻防都有非常成熟的案例，我个人觉得只要我们在开发的时候稍微留点心，就可以防住绝大多数的攻击。</p><hr><h3 id="用户账号体系安全"><a href="#用户账号体系安全" class="headerlink" title="用户账号体系安全"></a>用户账号体系安全</h3><p>任何一家互联网公司或者软件公司都会遇到用户账号体系设计的需求，尤为重要的就是用户密码的安全。很多知名的互联网公司用户库的泄露更是给我们敲响了警钟，所以我们需要设计一套即使账号体系发生泄露，也不会造成较大的危害，即泄露也不能登录用户账号。关于账号体系的设计，网上的方案有很多，我觉得主要需要做好如下几点。</p><ul><li>千万不要自己去写<strong>hash</strong>函数，如果你自信到能够写出<strong>SHA256</strong>、<strong>SHA512</strong>那就另当别论，建议使用SHA256、SHA512、RipeMD、WHIRLPOOL、SHA3等经过验证的hash函数，至于<del>MD5</del>、<del>SHA1</del>这些毕竟有点老了建议不使用。</li><li>为了防止查表（包含彩虹表）破解、暴力破解，所以一定需要加盐。首先盐不能复用，每个用户一个盐，密码发生变动盐也要同步修改；其次盐不能被预测，所以建议使用<a href="http://baike.baidu.com/view/1127.htm" target="_blank" rel="noopener">伪随机数生成器(CSPRNG)</a>，各个平台都有相应的技术解决方案。至于盐放到密码后面还是前面，这个都可以，只要保持风格一致就行。</li><li>hash一定要在服务端做，即使前端JS做了hash，前端hash依赖于JS，如果前端禁用JS，后端还需要模拟前端的hash。前端的盐千万别向后端请求去获取用户的盐，前端的盐可以用用户名+特定的字符串的规则来实现。</li><li>尽可能使用慢速hash函数，让破解的代价高昂到不可以接收，所以我们可以使用key扩展的hash函数，可以使用<a href="http://en.wikipedia.org/wiki/PBKDF2" target="_blank" rel="noopener">PBKDF2</a>或者<a href="http://en.wikipedia.org/wiki/Bcrypt" target="_blank" rel="noopener">bcrypt</a>，虽然慢速hash可能导致DDoS攻击，但是我们可以让迭代次数少一点或者在JS端来运行hash函数，如<a href="http://crypto.stanford.edu/sjcl/" target="_blank" rel="noopener">Stanford JavaScript Crypto Library</a>。</li><li>密码重置的时候，随机token一定要跟账户绑定并设置时效，因为SMTP是明文传输协议，防止SMTP导致安全问题。</li><li>客户端hash并不能代替TLS，建议为了防止中间人攻击，认证等敏感信息接口还是走TLS。</li></ul><p>我平时更多的是使用Spring-security中提供的<code>BCryptPasswordEncoder</code>，它是OpenBSD-style Blowfish的实现，盐都不用保存了，它直接帮我搞定非常方便，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.yangguo.demo.security;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"><span class="keyword">import</span> org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>:杨果</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span>:15/4/19 上午10:04</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Description:</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BcryptTest</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String password = <span class="string">"yangguo"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testEncode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        BCryptPasswordEncoder bCryptPasswordEncoder = <span class="keyword">new</span> BCryptPasswordEncoder();</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            String encodePassword=bCryptPasswordEncoder.encode(password);</span><br><span class="line">            System.out.println(encodePassword);</span><br><span class="line">            System.out.println(bCryptPasswordEncoder.matches(password,encodePassword));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h4 id="CSPRNG方案"><a href="#CSPRNG方案" class="headerlink" title="CSPRNG方案"></a>CSPRNG方案</h4><table><thead><tr><th>platform</th><th>CSPRNG</th></tr></thead><tbody><tr><td>PHP</td><td><a href="http://php.net/manual/en/function.mcrypt-create-iv.php" target="_blank" rel="noopener">PHP    mcrypt_create_iv</a>,<a href="http://php.net/manual/en/function.openssl-random-pseudo-bytes.php" target="_blank" rel="noopener">openssl_random_pseudo_bytes</a></td></tr><tr><td>Java</td><td><a href="http://docs.oracle.com/javase/6/docs/api/java/security/SecureRandom.html" target="_blank" rel="noopener">java.security.SecureRandom</a></td></tr><tr><td>Dot NET (C#, VB)</td><td><a href="http://msdn.microsoft.com/en-us/library/system.security.cryptography.rngcryptoserviceprovider.aspx" target="_blank" rel="noopener">System.Security.Cryptography.RNGCryptoServiceProvider</a></td></tr><tr><td>Ruby</td><td><a href="http://rubydoc.info/stdlib/securerandom/1.9.3/SecureRandom" target="_blank" rel="noopener">SecureRandom</a></td></tr><tr><td>Python</td><td><a href="http://docs.python.org/library/os.html" target="_blank" rel="noopener">os.urandom</a></td></tr><tr><td>Perl</td><td><a href="http://search.cpan.org/~mkanat/Math-Random-Secure-0.06/lib/Math/Random/Secure.pm" target="_blank" rel="noopener">Math::Random::Secure</a></td></tr><tr><td>C/C++ (Windows API)</td><td><a href="http://en.wikipedia.org/wiki/CryptGenRandom" target="_blank" rel="noopener">CryptGenRandom</a></td></tr><tr><td>Any language on GNU/Linux or Unix</td><td>Read from <a href="http://en.wikipedia.org/wiki//dev/random" target="_blank" rel="noopener">/dev/random</a> or /dev/urandom</td></tr></tbody></table><hr><h4 id="PHP-PBKDF2-密码hash代码"><a href="#PHP-PBKDF2-密码hash代码" class="headerlink" title="PHP PBKDF2 密码hash代码"></a>PHP PBKDF2 密码hash代码</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).</span></span><br><span class="line"><span class="comment"> * Copyright (c) 2013, Taylor Hornby</span></span><br><span class="line"><span class="comment"> * All rights reserved.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Redistribution and use in source and binary forms, with or without</span></span><br><span class="line"><span class="comment"> * modification, are permitted provided that the following conditions are met:</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 1. Redistributions of source code must retain the above copyright notice,</span></span><br><span class="line"><span class="comment"> * this list of conditions and the following disclaimer.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 2. Redistributions in binary form must reproduce the above copyright notice,</span></span><br><span class="line"><span class="comment"> * this list of conditions and the following disclaimer in the documentation</span></span><br><span class="line"><span class="comment"> * and/or other materials provided with the distribution.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"</span></span><br><span class="line"><span class="comment"> * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE</span></span><br><span class="line"><span class="comment"> * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span></span><br><span class="line"><span class="comment"> * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE</span></span><br><span class="line"><span class="comment"> * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR</span></span><br><span class="line"><span class="comment"> * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF</span></span><br><span class="line"><span class="comment"> * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS</span></span><br><span class="line"><span class="comment"> * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN</span></span><br><span class="line"><span class="comment"> * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)</span></span><br><span class="line"><span class="comment"> * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span></span><br><span class="line"><span class="comment"> * POSSIBILITY OF SUCH DAMAGE.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// These constants may be changed without breaking existing hashes.</span></span><br><span class="line">define(<span class="string">"PBKDF2_HASH_ALGORITHM"</span>, <span class="string">"sha256"</span>);</span><br><span class="line">define(<span class="string">"PBKDF2_ITERATIONS"</span>, <span class="number">1000</span>);</span><br><span class="line">define(<span class="string">"PBKDF2_SALT_BYTE_SIZE"</span>, <span class="number">24</span>);</span><br><span class="line">define(<span class="string">"PBKDF2_HASH_BYTE_SIZE"</span>, <span class="number">24</span>);</span><br><span class="line"></span><br><span class="line">define(<span class="string">"HASH_SECTIONS"</span>, <span class="number">4</span>);</span><br><span class="line">define(<span class="string">"HASH_ALGORITHM_INDEX"</span>, <span class="number">0</span>);</span><br><span class="line">define(<span class="string">"HASH_ITERATION_INDEX"</span>, <span class="number">1</span>);</span><br><span class="line">define(<span class="string">"HASH_SALT_INDEX"</span>, <span class="number">2</span>);</span><br><span class="line">define(<span class="string">"HASH_PBKDF2_INDEX"</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">create_hash</span><span class="params">($password)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// format: algorithm:iterations:salt:hash</span></span><br><span class="line">    $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));</span><br><span class="line">    <span class="keyword">return</span> PBKDF2_HASH_ALGORITHM . <span class="string">":"</span> . PBKDF2_ITERATIONS . <span class="string">":"</span> .  $salt . <span class="string">":"</span> .</span><br><span class="line">        base64_encode(pbkdf2(</span><br><span class="line">            PBKDF2_HASH_ALGORITHM,</span><br><span class="line">            $password,</span><br><span class="line">            $salt,</span><br><span class="line">            PBKDF2_ITERATIONS,</span><br><span class="line">            PBKDF2_HASH_BYTE_SIZE,</span><br><span class="line">            <span class="keyword">true</span></span><br><span class="line">        ));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate_password</span><span class="params">($password, $correct_hash)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    $params = explode(<span class="string">":"</span>, $correct_hash);</span><br><span class="line">    <span class="keyword">if</span>(count($params) &lt; HASH_SECTIONS)</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);</span><br><span class="line">    <span class="keyword">return</span> slow_equals(</span><br><span class="line">        $pbkdf2,</span><br><span class="line">        pbkdf2(</span><br><span class="line">            $params[HASH_ALGORITHM_INDEX],</span><br><span class="line">            $password,</span><br><span class="line">            $params[HASH_SALT_INDEX],</span><br><span class="line">            (int)$params[HASH_ITERATION_INDEX],</span><br><span class="line">            strlen($pbkdf2),</span><br><span class="line">            <span class="keyword">true</span></span><br><span class="line">        )</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Compares two strings $a and $b in length-constant time.</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">slow_equals</span><span class="params">($a, $b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    $diff = strlen($a) ^ strlen($b);</span><br><span class="line">    <span class="keyword">for</span>($i = <span class="number">0</span>; $i &lt; strlen($a) &amp;&amp; $i &lt; strlen($b); $i++)</span><br><span class="line">    &#123;</span><br><span class="line">        $diff |= ord($a[$i]) ^ ord($b[$i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> $diff === <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt</span></span><br><span class="line"><span class="comment"> * $algorithm - The hash algorithm to use. Recommended: SHA256</span></span><br><span class="line"><span class="comment"> * $password - The password.</span></span><br><span class="line"><span class="comment"> * $salt - A salt that is unique to the password.</span></span><br><span class="line"><span class="comment"> * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.</span></span><br><span class="line"><span class="comment"> * $key_length - The length of the derived key in bytes.</span></span><br><span class="line"><span class="comment"> * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.</span></span><br><span class="line"><span class="comment"> * Returns: A $key_length-byte key derived from the password and salt.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * This implementation of PBKDF2 was originally created by https://defuse.ca</span></span><br><span class="line"><span class="comment"> * With improvements by http://www.variations-of-shadow.com</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pbkdf2</span><span class="params">($algorithm, $password, $salt, $count, $key_length, $raw_output = false)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    $algorithm = strtolower($algorithm);</span><br><span class="line">    <span class="keyword">if</span>(!in_array($algorithm, hash_algos(), <span class="keyword">true</span>))</span><br><span class="line">        trigger_error(<span class="string">'PBKDF2 ERROR: Invalid hash algorithm.'</span>, E_USER_ERROR);</span><br><span class="line">    <span class="keyword">if</span>($count &lt;= <span class="number">0</span> || $key_length &lt;= <span class="number">0</span>)</span><br><span class="line">        trigger_error(<span class="string">'PBKDF2 ERROR: Invalid parameters.'</span>, E_USER_ERROR);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (function_exists(<span class="string">"hash_pbkdf2"</span>)) &#123;</span><br><span class="line">        <span class="comment">// The output length is in NIBBLES (4-bits) if $raw_output is false!</span></span><br><span class="line">        <span class="keyword">if</span> (!$raw_output) &#123;</span><br><span class="line">            $key_length = $key_length * <span class="number">2</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    $hash_length = strlen(hash($algorithm, <span class="string">""</span>, <span class="keyword">true</span>));</span><br><span class="line">    $block_count = ceil($key_length / $hash_length);</span><br><span class="line"></span><br><span class="line">    $output = <span class="string">""</span>;</span><br><span class="line">    <span class="keyword">for</span>($i = <span class="number">1</span>; $i &lt;= $block_count; $i++) &#123;</span><br><span class="line">        <span class="comment">// $i encoded as 4 bytes, big endian.</span></span><br><span class="line">        $last = $salt . pack(<span class="string">"N"</span>, $i);</span><br><span class="line">        <span class="comment">// first iteration</span></span><br><span class="line">        $last = $xorsum = hash_hmac($algorithm, $last, $password, <span class="keyword">true</span>);</span><br><span class="line">        <span class="comment">// perform the other $count - 1 iterations</span></span><br><span class="line">        <span class="keyword">for</span> ($j = <span class="number">1</span>; $j &lt; $count; $j++) &#123;</span><br><span class="line">            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, <span class="keyword">true</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        $output .= $xorsum;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>($raw_output)</span><br><span class="line">        <span class="keyword">return</span> substr($output, <span class="number">0</span>, $key_length);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> bin2hex(substr($output, <span class="number">0</span>, $key_length));</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><hr><h4 id="java-PBKDF2-密码hash代码"><a href="#java-PBKDF2-密码hash代码" class="headerlink" title="java PBKDF2 密码hash代码"></a>java PBKDF2 密码hash代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).</span></span><br><span class="line"><span class="comment"> * Copyright (c) 2013, Taylor Hornby</span></span><br><span class="line"><span class="comment"> * All rights reserved.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Redistribution and use in source and binary forms, with or without</span></span><br><span class="line"><span class="comment"> * modification, are permitted provided that the following conditions are met:</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 1. Redistributions of source code must retain the above copyright notice,</span></span><br><span class="line"><span class="comment"> * this list of conditions and the following disclaimer.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 2. Redistributions in binary form must reproduce the above copyright notice,</span></span><br><span class="line"><span class="comment"> * this list of conditions and the following disclaimer in the documentation</span></span><br><span class="line"><span class="comment"> * and/or other materials provided with the distribution.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"</span></span><br><span class="line"><span class="comment"> * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE</span></span><br><span class="line"><span class="comment"> * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span></span><br><span class="line"><span class="comment"> * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE</span></span><br><span class="line"><span class="comment"> * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR</span></span><br><span class="line"><span class="comment"> * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF</span></span><br><span class="line"><span class="comment"> * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS</span></span><br><span class="line"><span class="comment"> * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN</span></span><br><span class="line"><span class="comment"> * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)</span></span><br><span class="line"><span class="comment"> * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span></span><br><span class="line"><span class="comment"> * POSSIBILITY OF SUCH DAMAGE.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.security.SecureRandom;</span><br><span class="line"><span class="keyword">import</span> javax.crypto.spec.PBEKeySpec;</span><br><span class="line"><span class="keyword">import</span> javax.crypto.SecretKeyFactory;</span><br><span class="line"><span class="keyword">import</span> java.math.BigInteger;</span><br><span class="line"><span class="keyword">import</span> java.security.NoSuchAlgorithmException;</span><br><span class="line"><span class="keyword">import</span> java.security.spec.InvalidKeySpecException;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * PBKDF2 salted password hashing.</span></span><br><span class="line"><span class="comment"> * Author: havoc AT defuse.ca</span></span><br><span class="line"><span class="comment"> * www: http://crackstation.net/hashing-security.htm</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PasswordHash</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String PBKDF2_ALGORITHM = <span class="string">"PBKDF2WithHmacSHA1"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The following constants may be changed without breaking existing hashes.</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SALT_BYTE_SIZE = <span class="number">24</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> HASH_BYTE_SIZE = <span class="number">24</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> PBKDF2_ITERATIONS = <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> ITERATION_INDEX = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SALT_INDEX = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> PBKDF2_INDEX = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns a salted PBKDF2 hash of the password.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   password    the password to hash</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>              a salted PBKDF2 hash of the password</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">createHash</span><span class="params">(String password)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> createHash(password.toCharArray());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns a salted PBKDF2 hash of the password.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   password    the password to hash</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>              a salted PBKDF2 hash of the password</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">createHash</span><span class="params">(<span class="keyword">char</span>[] password)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="comment">// Generate a random salt</span></span><br><span class="line">        SecureRandom random = <span class="keyword">new</span> SecureRandom();</span><br><span class="line">        <span class="keyword">byte</span>[] salt = <span class="keyword">new</span> <span class="keyword">byte</span>[SALT_BYTE_SIZE];</span><br><span class="line">        random.nextBytes(salt);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Hash the password</span></span><br><span class="line">        <span class="keyword">byte</span>[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);</span><br><span class="line">        <span class="comment">// format iterations:salt:hash</span></span><br><span class="line">        <span class="keyword">return</span> PBKDF2_ITERATIONS + <span class="string">":"</span> + toHex(salt) + <span class="string">":"</span> +  toHex(hash);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Validates a password using a hash.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   password        the password to check</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   correctHash     the hash of the valid password</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>                  true if the password is correct, false if not</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">validatePassword</span><span class="params">(String password, String correctHash)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> validatePassword(password.toCharArray(), correctHash);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Validates a password using a hash.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   password        the password to check</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   correctHash     the hash of the valid password</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>                  true if the password is correct, false if not</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">validatePassword</span><span class="params">(<span class="keyword">char</span>[] password, String correctHash)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="comment">// Decode the hash into its parameters</span></span><br><span class="line">        String[] params = correctHash.split(<span class="string">":"</span>);</span><br><span class="line">        <span class="keyword">int</span> iterations = Integer.parseInt(params[ITERATION_INDEX]);</span><br><span class="line">        <span class="keyword">byte</span>[] salt = fromHex(params[SALT_INDEX]);</span><br><span class="line">        <span class="keyword">byte</span>[] hash = fromHex(params[PBKDF2_INDEX]);</span><br><span class="line">        <span class="comment">// Compute the hash of the provided password, using the same salt,</span></span><br><span class="line">        <span class="comment">// iteration count, and hash length</span></span><br><span class="line">        <span class="keyword">byte</span>[] testHash = pbkdf2(password, salt, iterations, hash.length);</span><br><span class="line">        <span class="comment">// Compare the hashes in constant time. The password is correct if</span></span><br><span class="line">        <span class="comment">// both hashes match.</span></span><br><span class="line">        <span class="keyword">return</span> slowEquals(hash, testHash);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Compares two byte arrays in length-constant time. This comparison method</span></span><br><span class="line"><span class="comment">     * is used so that password hashes cannot be extracted from an on-line</span></span><br><span class="line"><span class="comment">     * system using a timing attack and then attacked off-line.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   a       the first byte array</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   b       the second byte array</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>          true if both byte arrays are the same, false if not</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">slowEquals</span><span class="params">(<span class="keyword">byte</span>[] a, <span class="keyword">byte</span>[] b)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> diff = a.length ^ b.length;</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; a.length &amp;&amp; i &lt; b.length; i++)</span><br><span class="line">            diff |= a[i] ^ b[i];</span><br><span class="line">        <span class="keyword">return</span> diff == <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     *  Computes the PBKDF2 hash of a password.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   password    the password to hash.</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   salt        the salt</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   iterations  the iteration count (slowness factor)</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   bytes       the length of the hash to compute in bytes</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>              the PBDKF2 hash of the password</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] pbkdf2(<span class="keyword">char</span>[] password, <span class="keyword">byte</span>[] salt, <span class="keyword">int</span> iterations, <span class="keyword">int</span> bytes)</span><br><span class="line">        <span class="keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException</span><br><span class="line">    &#123;</span><br><span class="line">        PBEKeySpec spec = <span class="keyword">new</span> PBEKeySpec(password, salt, iterations, bytes * <span class="number">8</span>);</span><br><span class="line">        SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);</span><br><span class="line">        <span class="keyword">return</span> skf.generateSecret(spec).getEncoded();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Converts a string of hexadecimal characters into a byte array.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   hex         the hex string</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>              the hex string decoded into a byte array</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] fromHex(String hex)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">byte</span>[] binary = <span class="keyword">new</span> <span class="keyword">byte</span>[hex.length() / <span class="number">2</span>];</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; binary.length; i++)</span><br><span class="line">        &#123;</span><br><span class="line">            binary[i] = (<span class="keyword">byte</span>)Integer.parseInt(hex.substring(<span class="number">2</span>*i, <span class="number">2</span>*i+<span class="number">2</span>), <span class="number">16</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> binary;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Converts a byte array into a hexadecimal string.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   array       the byte array to convert</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>              a length*2 character string encoding the byte array</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> String <span class="title">toHex</span><span class="params">(<span class="keyword">byte</span>[] array)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        BigInteger bi = <span class="keyword">new</span> BigInteger(<span class="number">1</span>, array);</span><br><span class="line">        String hex = bi.toString(<span class="number">16</span>);</span><br><span class="line">        <span class="keyword">int</span> paddingLength = (array.length * <span class="number">2</span>) - hex.length();</span><br><span class="line">        <span class="keyword">if</span>(paddingLength &gt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">return</span> String.format(<span class="string">"%0"</span> + paddingLength + <span class="string">"d"</span>, <span class="number">0</span>) + hex;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="keyword">return</span> hex;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Tests the basic functionality of the PasswordHash class</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>   args        ignored</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">try</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// Print out 10 hashes</span></span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">                System.out.println(PasswordHash.createHash(<span class="string">"p\r\nassw0Rd!"</span>));</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Test password validation</span></span><br><span class="line">            <span class="keyword">boolean</span> failure = <span class="keyword">false</span>;</span><br><span class="line">            System.out.println(<span class="string">"Running tests..."</span>);</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++)</span><br><span class="line">            &#123;</span><br><span class="line">                String password = <span class="string">""</span>+i;</span><br><span class="line">                String hash = createHash(password);</span><br><span class="line">                String secondHash = createHash(password);</span><br><span class="line">                <span class="keyword">if</span>(hash.equals(secondHash)) &#123;</span><br><span class="line">                    System.out.println(<span class="string">"FAILURE: TWO HASHES ARE EQUAL!"</span>);</span><br><span class="line">                    failure = <span class="keyword">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                String wrongPassword = <span class="string">""</span>+(i+<span class="number">1</span>);</span><br><span class="line">                <span class="keyword">if</span>(validatePassword(wrongPassword, hash)) &#123;</span><br><span class="line">                    System.out.println(<span class="string">"FAILURE: WRONG PASSWORD ACCEPTED!"</span>);</span><br><span class="line">                    failure = <span class="keyword">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span>(!validatePassword(password, hash)) &#123;</span><br><span class="line">                    System.out.println(<span class="string">"FAILURE: GOOD PASSWORD NOT ACCEPTED!"</span>);</span><br><span class="line">                    failure = <span class="keyword">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(failure)</span><br><span class="line">                System.out.println(<span class="string">"TESTS FAILED!"</span>);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                System.out.println(<span class="string">"TESTS PASSED!"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span>(Exception ex)</span><br><span class="line">        &#123;</span><br><span class="line">            System.out.println(<span class="string">"ERROR: "</span> + ex);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h4 id="ASP-NET-C-密码hash代码"><a href="#ASP-NET-C-密码hash代码" class="headerlink" title="ASP.NET (C#)密码hash代码"></a>ASP.NET (C#)密码hash代码</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"># <span class="function">Password Hashing With <span class="title">PBKDF2</span> <span class="params">(http:<span class="comment">//crackstation.net/hashing-security.htm).</span></span></span></span><br><span class="line"># Copyright (c) 2013, Taylor Hornby</span><br><span class="line"># All rights reserved.</span><br><span class="line">#</span><br><span class="line"># Redistribution <span class="keyword">and</span> use in source <span class="keyword">and</span> binary forms, with <span class="keyword">or</span> without</span><br><span class="line"><span class="meta"># modification, are permitted provided that the following conditions are met:</span></span><br><span class="line">#</span><br><span class="line"># <span class="number">1.</span> Redistributions of source code must retain the above copyright notice,</span><br><span class="line"><span class="meta"># this list of conditions and the following disclaimer.</span></span><br><span class="line">#</span><br><span class="line"># <span class="number">2.</span> Redistributions in binary form must reproduce the above copyright notice,</span><br><span class="line"><span class="meta"># this list of conditions and the following disclaimer in the documentation</span></span><br><span class="line"><span class="meta"># and/or other materials provided with the distribution.</span></span><br><span class="line">#</span><br><span class="line"># THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS <span class="string">"AS IS"</span></span><br><span class="line"># AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE</span><br><span class="line"># IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE</span><br><span class="line"># ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE</span><br><span class="line"># LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR</span><br><span class="line"># <span class="function">CONSEQUENTIAL <span class="title">DAMAGES</span> <span class="params">(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF</span></span></span><br><span class="line"><span class="function"><span class="params"># SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS</span></span></span><br><span class="line"># INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN</span><br><span class="line"># CONTRACT, STRICT LIABILITY, <span class="function">OR <span class="title">TORT</span> <span class="params">(INCLUDING NEGLIGENCE OR OTHERWISE)</span></span></span><br><span class="line"># ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE</span><br><span class="line"># POSSIBILITY OF SUCH DAMAGE.</span><br><span class="line"></span><br><span class="line">require 'securerandom'</span><br><span class="line">require 'openssl'</span><br><span class="line">require 'base64'</span><br><span class="line"></span><br><span class="line"># Salted password hashing with PBKDF2-SHA1.</span><br><span class="line"># Authors: @RedragonX (dicesoft.net), havoc AT defuse.ca</span><br><span class="line"><span class="meta"># www: http:<span class="comment">//crackstation.net/hashing-security.htm</span></span></span><br><span class="line"><span class="keyword">module</span> PasswordHash</span><br><span class="line"></span><br><span class="line">  # The following constants can be changed without breaking existing hashes.</span><br><span class="line">  PBKDF2_ITERATIONS = <span class="number">1000</span></span><br><span class="line">  SALT_BYTE_SIZE = <span class="number">24</span></span><br><span class="line">  HASH_BYTE_SIZE = <span class="number">24</span></span><br><span class="line"></span><br><span class="line">  HASH_SECTIONS = <span class="number">4</span></span><br><span class="line">  SECTION_DELIMITER = <span class="string">':'</span></span><br><span class="line">  ITERATIONS_INDEX = <span class="number">1</span></span><br><span class="line">  SALT_INDEX = <span class="number">2</span></span><br><span class="line">  HASH_INDEX = <span class="number">3</span></span><br><span class="line"></span><br><span class="line">  # Returns a salted PBKDF2 hash of the password.</span><br><span class="line">  def self.createHash( password )</span><br><span class="line">    salt = SecureRandom.base64( SALT_BYTE_SIZE )</span><br><span class="line">    pbkdf2 = OpenSSL::PKCS5::pbkdf2_hmac_sha1(</span><br><span class="line">      password,</span><br><span class="line">      salt,</span><br><span class="line">      PBKDF2_ITERATIONS,</span><br><span class="line">      HASH_BYTE_SIZE</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> [<span class="string">"sha1"</span>, PBKDF2_ITERATIONS, salt, Base64.encode64( pbkdf2 )].join( SECTION_DELIMITER )</span><br><span class="line">  end</span><br><span class="line"></span><br><span class="line">  # Checks <span class="keyword">if</span> a password is correct given a hash of the correct one.</span><br><span class="line">  # correctHash must be a hash <span class="built_in">string</span> generated with createHash.</span><br><span class="line">  def self.validatePassword( password, correctHash )</span><br><span class="line">    params = correctHash.split( SECTION_DELIMITER )</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span> <span class="keyword">if</span> params.length != HASH_SECTIONS</span><br><span class="line"></span><br><span class="line">    pbkdf2 = Base64.decode64( params[HASH_INDEX] )</span><br><span class="line">    testHash = OpenSSL::PKCS5::pbkdf2_hmac_sha1(</span><br><span class="line">      password,</span><br><span class="line">      params[SALT_INDEX],</span><br><span class="line">      params[ITERATIONS_INDEX].to_i,</span><br><span class="line">      pbkdf2.length</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> pbkdf2 == testHash</span><br><span class="line">  end</span><br><span class="line"></span><br><span class="line">  # Run tests to ensure the <span class="keyword">module</span> is functioning properly.</span><br><span class="line">  # Returns <span class="literal">true</span> <span class="keyword">if</span> all tests succeed, <span class="literal">false</span> <span class="keyword">if</span> <span class="keyword">not</span>.</span><br><span class="line">  def self.runSelfTests</span><br><span class="line">    <span class="built_in">puts</span> <span class="string">"Sample hashes:"</span></span><br><span class="line">    <span class="number">3.</span>times &#123; <span class="built_in">puts</span> createHash(<span class="string">"password"</span>) &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">puts</span> <span class="string">"\nRunning self tests..."</span></span><br><span class="line">    @@allPass = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">    correctPassword = 'aaaaaaaaaa'</span><br><span class="line">    wrongPassword = 'aaaaaaaaab'</span><br><span class="line">    hash = createHash(correctPassword)</span><br><span class="line"></span><br><span class="line">    assert( validatePassword( correctPassword, hash ) == <span class="literal">true</span>, <span class="string">"correct password"</span> )</span><br><span class="line">    assert( validatePassword( wrongPassword, hash ) == <span class="literal">false</span>, <span class="string">"wrong password"</span> )</span><br><span class="line"></span><br><span class="line">    h1 = hash.split( SECTION_DELIMITER )</span><br><span class="line">    h2 = createHash( correctPassword ).split( SECTION_DELIMITER )</span><br><span class="line">    assert( h1[HASH_INDEX] != h2[HASH_INDEX], <span class="string">"different hashes"</span> )</span><br><span class="line">    assert( h1[SALT_INDEX] != h2[SALT_INDEX], <span class="string">"different salt"</span> )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> @@allPass</span><br><span class="line">      <span class="built_in">puts</span> <span class="string">"*** ALL TESTS PASS ***"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">      <span class="built_in">puts</span> <span class="string">"*** FAILURES ***"</span></span><br><span class="line">    end</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> @@allPass</span><br><span class="line">  end</span><br><span class="line"></span><br><span class="line">  def self.assert( truth, msg )</span><br><span class="line">    <span class="keyword">if</span> truth</span><br><span class="line">      <span class="built_in">puts</span> <span class="string">"PASS [#&#123;msg&#125;]"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">      <span class="built_in">puts</span> <span class="string">"FAIL [#&#123;msg&#125;]"</span></span><br><span class="line">      @@allPass = <span class="literal">false</span></span><br><span class="line">    end</span><br><span class="line">  end</span><br><span class="line"></span><br><span class="line">end</span><br><span class="line"></span><br><span class="line">PasswordHash.runSelfTests</span><br></pre></td></tr></table></figure><hr><h3 id="XSS防御"><a href="#XSS防御" class="headerlink" title="XSS防御"></a>XSS防御</h3><p>XSS漏洞千奇百怪，我们要防止XSS的攻击，可以遵循下面的一些原则：</p><ul><li>尽量不要页面中插入不可信的数据，如果迫不得已要插入，我们需要进行编码，编码使用OWASP提供的<a href="https://www.owasp.org/index.php/ESAPI" target="_blank" rel="noopener">ESAPI</a>的函数来实现，最好别自己实现。</li><li>如果需要将不可信任的数据插入到HTML标签之间，需要对这些数据进行HTML Entity编码，ESAPI使用如下：</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));</span><br></pre></td></tr></table></figure><ul><li>将不可信的数据插入到HTML属性里的时候，需要进行HTML属性编码，ESAPI使用如下：</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));</span><br></pre></td></tr></table></figure><ul><li>在将不可信数据插入到SCRIPT里时，需要对这些数据进行SCRIPT编码，ESAPI使用如下：</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));</span><br></pre></td></tr></table></figure><ul><li>在将不可信数据插入到Style属性里时，对这些数据进行CSS编码，ESAPI使用如下：</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));</span><br></pre></td></tr></table></figure><ul><li>在将不可信数据插入到HTML URL里时，对这些数据进行URL编码，我们可以进行URL编码和URL可信度的检查，ESAPI使用如下：</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">String userProvidedURL = request.getParameter(“userProvidedURL”);<span class="keyword">boolean</span> isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, <span class="number">255</span>, <span class="keyword">false</span>);</span><br><span class="line"><span class="keyword">if</span> (isValidURL) &#123;</span><br><span class="line">&lt;a href=”&lt;%= encoder.encodeForHTMLAttribute(userProvidedURL) %&gt;”&gt;&lt;/a&gt;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>使用富文本时，使用XSS规则引擎进行编码过滤，推荐使用OWASP AntiSamp或者Java HTML Sanitizer。</p></li><li><p>尽可能将Cookie设置为HttpOnly。</p></li><li><p>不论cookie还是localStorage都别存储敏感信息。</p></li></ul><hr><h3 id="CSRF防御"><a href="#CSRF防御" class="headerlink" title="CSRF防御"></a>CSRF防御</h3><p>跨站请求伪造，<code>就是利用你的身份，以你的名义来发送而已请求</code>，例如：发送邮件，购买商品，转账。CSRF攻击步骤分为以下两步：</p><ol><li>用户登录被攻击的网站A，并且A网站将cookie保存在用户浏览器。</li><li>在没有登出A的情况下，访问了恶意网站B。</li></ol><p>防御手段如下：</p><ol><li>良好的API设计，最好遵循Restful风格。GET操作只是获取资源，不能操作资源，它具备幂等性。如果需要对资源进行操作，请使用POST请求，当然你如果想使用PUT和DELETE，可以使用POST来模拟。</li><li>Cookie Hashing，由于同源策略，攻击者获取不到被攻击网站的cookie（理论上），因此就不能正确构造表单数据，所以攻击就失败了。但是如果攻击者先通过XSS获取到cookie，然后在结合CSRF来攻击，就有可能成功。这种方式是最简单可行的方案，基本可以抵挡绝大部分的攻击。</li><li>验证码，可以完全解决CSRF，但是体验会非常差。</li><li>One Time Token，其实是方案2的升级，但是得注意用户开多个页来浏览站点的情况，可以通过为一个用户授权多个token来解决，但是得设置生命周期。</li><li>验证HTTP头中的refer。</li></ol><hr><h3 id="Cookie使用注意事项"><a href="#Cookie使用注意事项" class="headerlink" title="Cookie使用注意事项"></a>Cookie使用注意事项</h3><ol><li>尽可能将cookie设置为Http Only，这样可以降低XSS攻击概率。</li><li>如果cookie是由HTTPS设置的，那么将cookie设置为secure，防止HTTP 301重定向导致cookie泄露。</li></ol><hr><h3 id="防点击劫持和拖放的欺骗劫持"><a href="#防点击劫持和拖放的欺骗劫持" class="headerlink" title="防点击劫持和拖放的欺骗劫持"></a>防点击劫持和拖放的欺骗劫持</h3><p>1.<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options" target="_blank" rel="noopener">X-Frame-Options</a>。<br>2.<code>if (top !==window)  top.location = window.location.href;</code>(攻击者可以使用204转向或者禁用Javascript的方式来绕过（例如iframe沙箱）)。</p><hr><p>参考文章:<a href="https://crackstation.net/hashing-security.htm" target="_blank" rel="noopener">hashing-security</a></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近几年层出不穷的安全漏洞，再一次要求大家提高安全意识。安全是很大的一个话题，下面是我平时工作总结出来的一些安全方面的经验。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;关闭错误显示&quot;&gt;&lt;a href=&quot;#关闭错误显示&quot; class=&quot;headerlink&quot; title=&quot;关闭错误显示&quot;&gt;&lt;/a&gt;关闭错误显示&lt;/h3&gt;&lt;p&gt;很多网站不知道是运维人员的业余还是疏忽，网站的错误显示没有关闭。攻击者只需要通过调整参数，就可以获得错误的堆栈信息，从而推断出网站使用的相关技术栈，从而为后面的攻击大开方便之门。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="安全" scheme="https://www.yangguo.info/tags/%E5%AE%89%E5%85%A8/"/>
    
      <category term="Cookie" scheme="https://www.yangguo.info/tags/Cookie/"/>
    
      <category term="XSS" scheme="https://www.yangguo.info/tags/XSS/"/>
    
      <category term="CSRF" scheme="https://www.yangguo.info/tags/CSRF/"/>
    
      <category term="劫持" scheme="https://www.yangguo.info/tags/%E5%8A%AB%E6%8C%81/"/>
    
      <category term="注入" scheme="https://www.yangguo.info/tags/%E6%B3%A8%E5%85%A5/"/>
    
      <category term="SQL" scheme="https://www.yangguo.info/tags/SQL/"/>
    
  </entry>
  
  <entry>
    <title>2015年工作总结</title>
    <link href="https://www.yangguo.info/2016/02/05/2015%E5%B9%B4%E5%B7%A5%E4%BD%9C%E6%80%BB%E7%BB%93/"/>
    <id>https://www.yangguo.info/2016/02/05/2015年工作总结/</id>
    <published>2016-02-05T06:11:45.000Z</published>
    <updated>2020-06-18T14:15:23.401Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2016农历新年假期前1个小时，心思已不专注，随意一写，简单回顾自己的2015！</p></blockquote><h3 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h3><p>如果用一个词来形容2015的工作表现，我觉得<code>波澜不惊</code>应该最为合适。没有大风大浪，没有力挽狂澜，工作按照原有节奏和既定部署开展。在没有重大项目的情况下，我们将大量的精力放在了系统架构升级和业务逻辑优化上面。机票核心系统经过2年的快速迭代和人员的变更，已经有了较多的技术负债，确保系统稳定的前提下并进行技术改造是我们面临的主要课题。</p><p>2014年机票核心模块绝大部分进行了服务化，业务方面带来的好处就是业务划分的更加合理，开发人员更加专注于自己的领域，技术方面就是服务的管理、降级、流控、监控、AB Test等更加容易。14年的成功，让我们对遗留核心业务的改造充满了信心。记忆尤为深刻的是一个<strong>PNR</strong>解析程序当时7000行的代码，各种业务规则的判断，各种语法分析逻辑，我们优化到不到1000行。这在当时绝对是一件值得骄傲的事情，虽然看似简单的一个类，却承载着公司每天5千万交易量的订单生成和2年的领域知识沉淀，之前这个类是很多人都不敢碰，因为这个风险不是谁都愿意承担的。虽然主导者是我，但是操刀者却不是我，这件事情放在14年绝对不敢想象，因为这种核心的业务我是绝对不放心交给下面的小伙伴的。但事实证明只要给他们机会和时间，他们是能够成长起来的，就如当年我的领导信任我一样。在管理上更加注重小伙伴们的发展，让他们快乐的成长，因为他们唯一能够带走的就是自己的能力。当然15年在对外的业务沟通和对内的技术指导，都更加内敛不再锋芒毕露，毕竟谁会拒绝一个好沟通好相处的同事、下属或者领导呢？</p><p>由于工作的变动，16年注定是机遇和挑战并存的一年。从参加工作以来，一直在专注于技术深度的挖掘，在广度的拓展上还需要投入更多精力。16年在做好后端架构本职工作的基础上，更多的是与移动端、web端、测试、运维相互交流学习，创建良好的技术氛围提升公司的技术实力。希望通过这一年的努力，让自己成为一个能够影响他人能够带着大家往前走的人，不仅仅是技术方面。</p><a id="more"></a><hr><h3 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h3><p>都说码农没有什么业余生活，这句话放在2年前，我是认同的。毕竟在这样一个日新月异的行业混饭吃，就如逆水行舟不进则退。要想变得优秀，你就得付出更多的业余时间，当然也有能力超强只需要付出极少努力就能很牛逼的人，但是绝大多数人不在此列，当然我是属于那绝大多数里面的一员。由于2年前女儿的降临，15年我加班的时间明显减少了很多，并没有什么深刻的人生领悟促使我这样。我只是希望能够陪她吃晚饭，在她睡觉前能够看见爸爸，仅此而已。周末需要抽出更多的时间陪小孩，而不只是抱着电脑一坐就是一天，每次回头翻看定格在照片中的那一张张开心的笑脸，还有什么能够形容那种美妙的感觉呢？养儿不易，所以更能够体会父母的艰辛，他们对我的需要就如当年我对他们的需要一样，所以这一年跟父母的电话明显多了很多。陪伴家人的时间多了，你会发现思考和学习的时间少了很多，好在我坚持使用<a href="http://www.publicspace.net/Vitamin-R/" target="_blank" rel="noopener">Vitamin-R</a>的确提升了我不少的效率。最后结果非常不错，并没有因为加班少了影响工作的开展，高度的精神集中使我效率的提升到了平时的2-3倍，这是我15年非常开心的一件事情。虽然这一年收获不少，但是同样也有让我不是很满意的地方，如：阅读量不达标，并且英语学习三天打鱼两天晒网，办的健身卡也不幸蒙尘。但愿16年希望将15年做的好的方面继续保持，将做的不好的继续改进。虽然我坚信生活不能事事都如自己所愿，但是希望16年能够获得一个较好的平衡。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;2016农历新年假期前1个小时，心思已不专注，随意一写，简单回顾自己的2015！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;工作&quot;&gt;&lt;a href=&quot;#工作&quot; class=&quot;headerlink&quot; title=&quot;工作&quot;&gt;&lt;/a&gt;工作&lt;/h3&gt;&lt;p&gt;如果用一个词来形容2015的工作表现，我觉得&lt;code&gt;波澜不惊&lt;/code&gt;应该最为合适。没有大风大浪，没有力挽狂澜，工作按照原有节奏和既定部署开展。在没有重大项目的情况下，我们将大量的精力放在了系统架构升级和业务逻辑优化上面。机票核心系统经过2年的快速迭代和人员的变更，已经有了较多的技术负债，确保系统稳定的前提下并进行技术改造是我们面临的主要课题。&lt;/p&gt;
&lt;p&gt;2014年机票核心模块绝大部分进行了服务化，业务方面带来的好处就是业务划分的更加合理，开发人员更加专注于自己的领域，技术方面就是服务的管理、降级、流控、监控、AB Test等更加容易。14年的成功，让我们对遗留核心业务的改造充满了信心。记忆尤为深刻的是一个&lt;strong&gt;PNR&lt;/strong&gt;解析程序当时7000行的代码，各种业务规则的判断，各种语法分析逻辑，我们优化到不到1000行。这在当时绝对是一件值得骄傲的事情，虽然看似简单的一个类，却承载着公司每天5千万交易量的订单生成和2年的领域知识沉淀，之前这个类是很多人都不敢碰，因为这个风险不是谁都愿意承担的。虽然主导者是我，但是操刀者却不是我，这件事情放在14年绝对不敢想象，因为这种核心的业务我是绝对不放心交给下面的小伙伴的。但事实证明只要给他们机会和时间，他们是能够成长起来的，就如当年我的领导信任我一样。在管理上更加注重小伙伴们的发展，让他们快乐的成长，因为他们唯一能够带走的就是自己的能力。当然15年在对外的业务沟通和对内的技术指导，都更加内敛不再锋芒毕露，毕竟谁会拒绝一个好沟通好相处的同事、下属或者领导呢？&lt;/p&gt;
&lt;p&gt;由于工作的变动，16年注定是机遇和挑战并存的一年。从参加工作以来，一直在专注于技术深度的挖掘，在广度的拓展上还需要投入更多精力。16年在做好后端架构本职工作的基础上，更多的是与移动端、web端、测试、运维相互交流学习，创建良好的技术氛围提升公司的技术实力。希望通过这一年的努力，让自己成为一个能够影响他人能够带着大家往前走的人，不仅仅是技术方面。&lt;/p&gt;
    
    </summary>
    
      <category term="生活" scheme="https://www.yangguo.info/categories/life/"/>
    
    
      <category term="工作总结" scheme="https://www.yangguo.info/tags/%E5%B7%A5%E4%BD%9C%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>基于Graphite的监控方案</title>
    <link href="https://www.yangguo.info/2015/08/17/%E5%9F%BA%E4%BA%8EGraphite%E7%9A%84%E7%9B%91%E6%8E%A7%E6%96%B9%E6%A1%88/"/>
    <id>https://www.yangguo.info/2015/08/17/基于Graphite的监控方案/</id>
    <published>2015-08-17T04:54:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><p>公司目前的监控粒度非常粗放，只做了些常规的监控，如：服务器的CPU/磁盘/网络，数据库，消息队列，以及一些核心业务的健康检查等。这种粒度的监控对发展初期的创业公司来说已经足够，但是对于接下来的发展便开始捉襟见肘了。个人认为监控不能浮于形式，不是采集一堆数据进行简单的罗列或者生成Dashboard，而是能够通过这些数据产生决策，告诉我们哪里出了问题，该如何解决，甚至自动解决。所以我们要能够自动采集系统中的各种<strong>metric</strong>，然后各种<strong>metric</strong>汇集到数据中心进行处理，最后便是展现、报警以及自动修复等逻辑。</p><hr><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p>关于监控，业界成熟的方案数不胜数，如何从中选择适合自己的解决方案便是其中的难点。确定方案之前，我们首先需要对自己的系统架构进行梳理，确定我们所选方案兼容目前架构。由于我们的系统基本全部都构建在一系列的Java技术栈之上，Zabbix已经广泛应用在各个系统之中，所以我们做了如下的几点约定：</p><ol><li><strong>Metric</strong>的获取只能用Java实现</li><li>服务器以及一些中间件的常规监控延续使用<strong>Zabbix</strong></li><li>报警逻辑使用<strong>Zabbix</strong>实现</li><li>数据能够实现多个维度的自由组合叠加以及横向对比</li><li><strong>Metric</strong>获取的侵入性要非常小</li></ol><a id="more"></a><hr><h3 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h3><p>经过了前面两步，其实后面的方案选择就非常简单了，最终我们新版的监控架构图如下：</p><p><img src="/images/MonitorStructure.png" alt="MonitorStructure"></p><ul><li><a href="http://metrics.dropwizard.io/" target="_blank" rel="noopener">Metrics</a>：获取JVM中的指标项(metric)，并将指标项发布到Graphite。</li><li><a href="http://graphite.readthedocs.org/en/latest/" target="_blank" rel="noopener">Graphite</a>：数据收集处理以及对外提供接口。</li><li><a href="http://grafana.org/" target="_blank" rel="noopener">Grafana</a>：通过Graphite-web接口获取数据进行图表展示。</li><li><a href="https://www.zabbix.org/wiki/Main_Page" target="_blank" rel="noopener">Zabbix</a>：通过Graphite-web接口获取数据，来进行报警以及服务器常规监控。</li></ul><hr><h3 id="实施"><a href="#实施" class="headerlink" title="实施"></a>实施</h3><p>方案确定之后，便可以开始实施了。首先需要将环境配置起来，然后才是代码层面的嵌入。<strong>Graphite</strong>的安装不是一件容易的事情，各个平台的安装都不相同，你可以把这当做是一种痛并快乐着的享受，下面便是我在Ubuntu上进行环境配置的步骤，仅供参考：</p><h4 id="Graphite安装"><a href="#Graphite安装" class="headerlink" title="Graphite安装"></a>Graphite安装</h4><ol><li>sudo apt-get install nginx uwsgi uwsgi-plugin-python</li><li>sudo apt-get install graphite-web graphite-carbon</li></ol><p>安装就上面两步，当然在安装的过程中，缺少什么安装下就行了，接下来便可以开始配置Graphite。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line">1. sudo vim /etc/graphite/local_settings.py</span><br><span class="line"><span class="comment">#将时区修改为正确的时区</span></span><br><span class="line">TIME_ZONE = <span class="string">'Asia/Shanghai'</span></span><br><span class="line"><span class="comment">#配置数据库，当然你也可以使用默认的sqlite3</span></span><br><span class="line">DATABASES = &#123;</span><br><span class="line">    <span class="string">'default'</span>: &#123;</span><br><span class="line">        <span class="string">'NAME'</span>: <span class="string">'graphite'</span>,</span><br><span class="line">        <span class="string">'ENGINE'</span>: <span class="string">'django.db.backends.postgresql_psycopg2'</span>,</span><br><span class="line">        <span class="string">'USER'</span>: <span class="string">'postgres'</span>,</span><br><span class="line">        <span class="string">'PASSWORD'</span>: <span class="string">'postgres'</span>,</span><br><span class="line">        <span class="string">'HOST'</span>: <span class="string">'localhost'</span>,</span><br><span class="line">        <span class="string">'PORT'</span>: <span class="string">''</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">2. 当配置好了之后，并可以执行`sudo graphite-manage syncdb`，进行数据库信息同步。</span><br><span class="line"></span><br><span class="line">3. sudo vim /etc/default/graphite-carbon</span><br><span class="line">CARBON_CACHE_ENABLED=<span class="literal">true</span></span><br><span class="line"></span><br><span class="line">4. sudo vim /etc/carbon/carbon.conf</span><br><span class="line"><span class="comment">#开启Carbon的log rotation</span></span><br><span class="line">ENABLE_LOGROTATION = True</span><br><span class="line"></span><br><span class="line">5. sudo vim /etc/carbon/storage-schemas.conf</span><br><span class="line"><span class="comment">#配置storage schemas</span></span><br><span class="line"><span class="comment">#意思是匹配任何以test.开头的metrics，采用三种方式收集存储数据：</span></span><br><span class="line"><span class="comment">#1.10s:10m---每10秒创建一个数据点，保存10分钟内该类型的数据点。</span></span><br><span class="line"><span class="comment">#2.1m:1h---每1分钟创建一个数据点，保存1小时内该类型的数据点。</span></span><br><span class="line"><span class="comment">#3.10m:1d---每10分钟创建一个数据点，保存一天内的该类型的数据点。</span></span><br><span class="line">[<span class="built_in">test</span>]</span><br><span class="line">pattern = ^<span class="built_in">test</span>\.</span><br><span class="line">retentions = 10s:10m,1m:1h,10m:1d</span><br><span class="line"></span><br><span class="line">6. sudo cp /usr/share/doc/graphite-carbon/examples/storage-aggregation.conf.example /etc/carbon/storage-aggregation.conf</span><br><span class="line"><span class="comment">#/etc/carbon/storage-aggregation.conf中的aggregation method默认使用的是average，其中的xFileFactor的值代表Carbon做聚合的最小百分比值，根据你的具体情况修改。</span></span><br><span class="line"></span><br><span class="line">7. sudo vim /etc/nginx/sites-available/graphite.conf</span><br><span class="line"><span class="comment">#nginx配置</span></span><br><span class="line">upstream graphite &#123;</span><br><span class="line">  server unix:///run/uwsgi/app/graphite/socket;</span><br><span class="line"></span><br><span class="line">  keepalive 600;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">  listen 80;</span><br><span class="line">  server_name graphite.yangguo.info;</span><br><span class="line"></span><br><span class="line">  access_log /var/<span class="built_in">log</span>/nginx/graphite.access.log;</span><br><span class="line">  error_log /var/<span class="built_in">log</span>/nginx/graphite.error.log;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  location / &#123;</span><br><span class="line">    uwsgi_pass graphite;</span><br><span class="line">    include uwsgi_params;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">8. sudo ln -s /etc/nginx/sites-available/graphite.conf /etc/nginx/sites-enabled</span><br><span class="line"></span><br><span class="line">9. sudo vi /etc/uwsgi/apps-available/graphite.ini</span><br><span class="line">[uwsgi]</span><br><span class="line">processes = 8</span><br><span class="line">uid = _graphite</span><br><span class="line">gid = _graphite</span><br><span class="line">master = True</span><br><span class="line"><span class="built_in">chdir</span> = /usr/share/graphite-web</span><br><span class="line">wsgi-file = graphite.wsgi</span><br><span class="line">chmod-socket = 666</span><br><span class="line"><span class="built_in">enable</span>-threads = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">10. sudo ln -s /etc/uwsgi/apps-available/graphite.ini /etc/uwsgi/apps-enabled</span><br><span class="line"></span><br><span class="line">11. sudo service carbon-cache start</span><br><span class="line">sudo service nginx start</span><br><span class="line">sudo service uwsgi start</span><br></pre></td></tr></table></figure><p>对于上面第五步metrics的配置，精度的具体配置得看自己的业务，并不是粒度越细越好。<strong>江南白衣</strong>有篇很好的文章：<a href="http://calvin1978.blogcn.com/articles/graphite.html" target="_blank" rel="noopener">Graphite的百万Metrics实践之路</a>，就直接引过来，希望对大家有帮助。Graphite的安装及配置并不是非常容易，如果碰见问题，查查资料便能够解决，特别注意各种版本的兼容问题。</p><h4 id="Whisper存储模式和聚合"><a href="#Whisper存储模式和聚合" class="headerlink" title="Whisper存储模式和聚合"></a>Whisper存储模式和聚合</h4><p>在开始的架构图中，你可能已经看见<strong>Graphite</strong>包含三个组件，<strong>Carbon</strong>和<strong>Graphite-web</strong>你可以不了解。但是如果你对<strong>Whisper</strong>的内部运行原理不是很了解，你可能会碰见如下的困惑：</p><ul><li>为什么我的数据点被平均了？</li><li>为什么我周期性的发布了数据点，却看不见任何数据？</li><li>我发布了很多天的数据，却只能看见一天的数据？</li><li>为什么看见的数据跟我发布的数据点好像对应不上？</li></ul><p>要解决以上的问题，首先我们要明白<strong>Whisper</strong>是如何存储数据的，然后要了解它的聚合规则，最后还有数据点的移动。只要掌握了这些，我们才能配置合理的存储模式以及聚合规则，从而得到我们想要的结果。InfoQ上的<a href="http://www.infoq.com/cn/articles/graphite-intro" target="_blank" rel="noopener">DevOps实战：Graphite监控上手指南</a>已经讲的非常透彻，此处本着不造轮子的原则，就不复述了。</p><h4 id="Grafana安装"><a href="#Grafana安装" class="headerlink" title="Grafana安装"></a>Grafana安装</h4><p>Grafana安装便非常简单，具体步骤如下：</p><ol><li>$ wget <a href="https://grafanarel.s3.amazonaws.com/builds/grafana_2.1.1_amd64.deb" target="_blank" rel="noopener">https://grafanarel.s3.amazonaws.com/builds/grafana_2.1.1_amd64.deb</a></li><li>$ sudo apt-get install -y adduser libfontconfig</li><li>$ sudo dpkg -i grafana_2.1.1_amd64.deb</li></ol><p>安装完成之后，便可以启动grafana-server了：</p><p>$ sudo service grafana-server start</p><p>最后设置开机启动：</p><p>$ sudo update-rc.d grafana-server defaults 95 10</p><p>别的环境安装教程见<a href="http://docs.grafana.org/installation/" target="_blank" rel="noopener">此处</a></p><p>Grafana安装完成之后，便可以通过配置<code>Data sources</code>了，然后自定义需要展示的<strong>Dashboard</strong>，至此<strong>Graphite</strong>和<strong>Grafana</strong>的安装与集成便完成了。</p><h4 id="Metrics使用"><a href="#Metrics使用" class="headerlink" title="Metrics使用"></a>Metrics使用</h4><p><strong>Metrics</strong>它不但提供了Gauge、Counter、Meter、Histogram、Timer等度量工具类以及Health Check功能，而且它还提供了很多模块为第三方库或者应用提供辅助统计信息，比如Jetty，Logback，Log4j，Apache HttpClient，Ehcache，JDBI，Jersey，最后它还可以将度量数据发送给Ganglia和Graphite以提供图形化的监控。作为一款监控指标的度量类库，从功能完善度已经易用性来说，它是表现的相当优秀。</p><p>具体的业务指标需要自己的定义，然后完成采集，如果你使用<strong>spring</strong>，你可以使用<strong>metrics-spring</strong>，这又可以减轻一部分你的工作量。具体代码如何与代码集成，阅读下相关文档就可以很快上手了。</p><h4 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h4><p>当完成上面三步，我们的数据采集，数据的汇总，数据展现都正常之后，我们就可以看看效果了，这是我简单的截了一个我的测试环境的图，供大家参考。<br><img src="/images/jvm.png" alt="jvm监控"><br><img src="/images/logic.png" alt="logic监控"></p><h4 id="监控"><a href="#监控" class="headerlink" title="监控"></a>监控</h4><p><strong>graphite</strong>之所以被大家广泛采用，我觉得<strong>graphite-web API</strong>功不可没，它可以根据你将你需要的数据通过PNG图片或者JSON返回给你。正式基于这点，我们可以非常方便的与自己的报警系统集成，我们可以细粒度的监控我们的每项指标，然后来指定报警规则，并可以通过图表+文字的形式发送出来。</p><p>请求1：<code>http://graphite.travelzen.com/render?target=jvm.192_168_161_87.ibe-interface-server.memory.heap.usage</code>得到了一个png图片</p><p><img src="/images/pic.png" alt="api图片"></p><p>请求2：<code>http://graphite.travelzen.com/render?target=jvm.192_168_161_87.ibe-interface-server.memory.heap.usage&amp;format=json</code>得到json数据</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">[&#123;&quot;target&quot;: &quot;jvm.192_168_161_87.ibe-interface-server.memory.heap.usage&quot;, &quot;datapoints&quot;: [[0.01583333333333333, 1440424200], [0.016166666666666666, 1440424800], [0.015999999999999997, 1440425400], [0.01583333333333333, 1440426000], [0.015999999999999997, 1440426600], [0.01583333333333333, 1440427200], [0.015999999999999997, 1440427800], [0.01583333333333333, 1440428400], [0.016166666666666666, 1440429000], [0.016166666666666666, 1440429600], [0.015999999999999997, 1440430200], [0.016000000000000004, 1440430800], [0.01633333333333333, 1440431400], [0.01633333333333333, 1440432000], [0.01616666666666667, 1440432600], [0.01616666666666667, 1440433200], [0.01583333333333333, 1440433800], [0.01616666666666667, 1440434400], [0.0165, 1440435000], [0.016000000000000004, 1440435600], [0.016, 1440436200], [0.016166666666666666, 1440436800], [0.01616666666666667, 1440437400], [0.01583333333333333, 1440438000], [0.01633333333333333, 1440438600], [0.01633333333333333, 1440439200], [0.015666666666666666, 1440439800], [0.01633333333333333, 1440440400], [0.016333333333333335, 1440441000], [0.01583333333333333, 1440441600], [0.01616666666666667, 1440442200], [0.015999999999999997, 1440442800], [0.01616666666666667, 1440443400], [0.015999999999999997, 1440444000], [0.015999999999999997, 1440444600], [0.016, 1440445200], [0.01616666666666667, 1440445800], [0.01583333333333333, 1440446400], [0.01616666666666667, 1440447000], [0.016000000000000004, 1440447600], [0.016000000000000004, 1440448200], [0.01616666666666667, 1440448800], [0.015999999999999997, 1440449400], [0.016, 1440450000], [0.01616666666666667, 1440450600], [0.01583333333333333, 1440451200], [0.01616666666666667, 1440451800], [0.016000000000000004, 1440452400], [0.016000000000000004, 1440453000], [0.016166666666666666, 1440453600], [0.01616666666666667, 1440454200], [0.016000000000000004, 1440454800], [0.016166666666666666, 1440455400], [0.01616666666666667, 1440456000], [0.01616666666666667, 1440456600], [0.01616666666666667, 1440457200], [0.015999999999999997, 1440457800], [0.016000000000000004, 1440458400], [0.01633333333333333, 1440459000], [0.015666666666666666, 1440459600], [0.01633333333333333, 1440460200], [0.01583333333333333, 1440460800], [0.01616666666666667, 1440461400], [0.016, 1440462000], [0.01616666666666667, 1440462600], [0.016166666666666666, 1440463200], [0.016, 1440463800], [0.01633333333333333, 1440464400], [0.01633333333333333, 1440465000], [0.0165, 1440465600], [0.01633333333333333, 1440466200], [0.017, 1440466800], [0.017, 1440467400], [0.017, 1440468000], [0.016666666666666663, 1440468600], [0.017, 1440469200], [0.016999999999999998, 1440469800], [0.016833333333333332, 1440470400], [0.017, 1440471000], [0.020500000000000004, 1440471600], [0.023, 1440472200], [0.02266666666666667, 1440472800], [0.023000000000000003, 1440473400], [0.02316666666666667, 1440474000], [0.022833333333333334, 1440474600], [0.023, 1440475200], [0.022500000000000003, 1440475800], [0.023, 1440476400], [0.022833333333333334, 1440477000], [0.022833333333333334, 1440477600], [0.022833333333333337, 1440478200], [0.022833333333333337, 1440478800], [0.022833333333333337, 1440479400], [0.023, 1440480000], [0.023, 1440480600], [0.022833333333333337, 1440481200], [0.023, 1440481800], [0.016166666666666666, 1440482400], [0.015000000000000003, 1440483000], [0.015000000000000003, 1440483600], [0.015000000000000003, 1440484200], [0.015000000000000003, 1440484800], [0.015333333333333332, 1440485400], [0.015333333333333334, 1440486000], [0.01633333333333333, 1440486600], [0.025, 1440487200], [0.024999999999999998, 1440487800], [0.025333333333333336, 1440488400], [0.025, 1440489000], [0.02533333333333333, 1440489600], [0.025333333333333336, 1440490200], [0.025, 1440490800], [0.024833333333333336, 1440491400], [0.02633333333333333, 1440492000], [0.026499999999999996, 1440492600], [0.02633333333333333, 1440493200], [0.027500000000000004, 1440493800], [0.026666666666666665, 1440494400], [0.026666666666666672, 1440495000], [0.025500000000000002, 1440495600], [0.025000000000000005, 1440496200], [0.025000000000000005, 1440496800], [0.025000000000000005, 1440497400], [0.025000000000000005, 1440498000], [0.025000000000000005, 1440498600], [0.025000000000000005, 1440499200], [0.025000000000000005, 1440499800], [0.025000000000000005, 1440500400], [0.025000000000000005, 1440501000], [0.025000000000000005, 1440501600], [0.025000000000000005, 1440502200], [0.025000000000000005, 1440502800], [0.025833333333333337, 1440503400], [0.027500000000000004, 1440504000], [0.027500000000000004, 1440504600], [0.027500000000000004, 1440505200], [0.027500000000000004, 1440505800], [0.027500000000000004, 1440506400], [0.027500000000000004, 1440507000], [0.027500000000000004, 1440507600], [0.027500000000000004, 1440508200], [0.025500000000000002, 1440508800], [0.02518518518518519, 1440509400], [null, 1440510000]]&#125;]</span><br></pre></td></tr></table></figure><p>我只是简单的说明了下API的使用，API还有非常多的函数可供选择，每次当你有新的需求不妨先翻翻是不是有现成的函数或者方法可供使用。</p><p>监控这个老生常谈的话题，其实没有多少干货，看似简单其实做好并不容易。很多东西并不复杂，最重要的是要契合自己的业务，知道自己的关注点。Enjoy yourself !</p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;需求&quot;&gt;&lt;a href=&quot;#需求&quot; class=&quot;headerlink&quot; title=&quot;需求&quot;&gt;&lt;/a&gt;需求&lt;/h3&gt;&lt;p&gt;公司目前的监控粒度非常粗放，只做了些常规的监控，如：服务器的CPU/磁盘/网络，数据库，消息队列，以及一些核心业务的健康检查等。这种粒度的监控对发展初期的创业公司来说已经足够，但是对于接下来的发展便开始捉襟见肘了。个人认为监控不能浮于形式，不是采集一堆数据进行简单的罗列或者生成Dashboard，而是能够通过这些数据产生决策，告诉我们哪里出了问题，该如何解决，甚至自动解决。所以我们要能够自动采集系统中的各种&lt;strong&gt;metric&lt;/strong&gt;，然后各种&lt;strong&gt;metric&lt;/strong&gt;汇集到数据中心进行处理，最后便是展现、报警以及自动修复等逻辑。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;分析&quot;&gt;&lt;a href=&quot;#分析&quot; class=&quot;headerlink&quot; title=&quot;分析&quot;&gt;&lt;/a&gt;分析&lt;/h3&gt;&lt;p&gt;关于监控，业界成熟的方案数不胜数，如何从中选择适合自己的解决方案便是其中的难点。确定方案之前，我们首先需要对自己的系统架构进行梳理，确定我们所选方案兼容目前架构。由于我们的系统基本全部都构建在一系列的Java技术栈之上，Zabbix已经广泛应用在各个系统之中，所以我们做了如下的几点约定：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Metric&lt;/strong&gt;的获取只能用Java实现&lt;/li&gt;
&lt;li&gt;服务器以及一些中间件的常规监控延续使用&lt;strong&gt;Zabbix&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;报警逻辑使用&lt;strong&gt;Zabbix&lt;/strong&gt;实现&lt;/li&gt;
&lt;li&gt;数据能够实现多个维度的自由组合叠加以及横向对比&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metric&lt;/strong&gt;获取的侵入性要非常小&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="监控" scheme="https://www.yangguo.info/tags/%E7%9B%91%E6%8E%A7/"/>
    
      <category term="Graphite" scheme="https://www.yangguo.info/tags/Graphite/"/>
    
  </entry>
  
  <entry>
    <title>漫谈IM通信架构</title>
    <link href="https://www.yangguo.info/2015/08/17/%E6%BC%AB%E8%B0%88%E9%80%9A%E8%AE%AF%E6%9E%B6%E6%9E%84/"/>
    <id>https://www.yangguo.info/2015/08/17/漫谈通讯架构/</id>
    <published>2015-08-17T04:54:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前前后后做的IM和推送系统已经有好几个了，一直都想好好总结下，因此就有了这篇文章。在我刚学编程的那会儿，觉得网络通信是一个很牛逼和门槛很高的一门技术，但是随着开源技术的发展和互联网知识的共享，现在要写出高质量的网络通信程序已经变得容易多了。</p></blockquote><p>只要谈通讯肯定绕不开协议，鉴于本人经验下面只谈本人撸过的三种协议：</p><ol><li>XMPP</li><li>MQTT</li><li>私有协议</li></ol><a id="more"></a><hr><h3 id="XMPP"><a href="#XMPP" class="headerlink" title="XMPP"></a>XMPP</h3><p>XMPP(Extensible Messaging and Presence Protocol)，也叫Jabber，它是基于稳定长连接网络环境所设计的，对于不够稳定和带宽小的移动网络不是非常合适。由于XMPP基于XML，所以流量大，流量问题对于移动网络来说非常敏感，然后就是消息不可靠、CMWAP兼容、开源项目对协议实现不完善等问题，也是XMPP面临的问题。当然XML可以通过精简压缩来实现流量可控，目前这也是XMPP优化的可行方案，消息的不可靠可以通过扩展XMPP来实现ACK，随着3/4G的发展，CMWAP网关毕竟是末日黄花，但是开源项目对协议只是部分实现等问题，也是使用XMPP绕不过去的坎。Openfire是XMPP领域最知名的开源项目，它简单易用，是很多团队的首选方案，这是国内使用最多的开源方案。Openfire虽然优点很多，但是缺点也不少，最致命的就是它的分布式扩展能力很弱，当用户量很大的时候，水平扩展就成为它的瓶颈所在。还有一个不得不提的项目就是<a href="http://www.tigase.net/" target="_blank" rel="noopener">Tigase</a>，这是笔者接触的第一个XMPP开源项目，它在分布式扩展能力上和架构设计上比Openfire强了不少。由于该项目开始是一个私人项目，现在好像在商业化，所以使用者并不是很多，虽然国外有成熟案例，但是国内目前并不多，所以当时趟了Tigase的很多坑，目前<a href="http://www.jk.cn/" target="_blank" rel="noopener">平安好医生</a>的聊天系统就是基于此搭建的。如果对Tigase感兴趣，可以阅读我之前写的一篇文章《<a href="/2014/09/20/tigase集群方案及配置说明文档">Tigase集群方案及配置说明文档</a>》。不论使用哪个开源项目，虽然看起来开箱即用，但是要成为稳定成熟的产品，还需要深度的二次开发才行。</p><p>虽然XMPP有很多弊端，但是它的生态目前是最完善的，如果从成本角度来考量，XMPP是前期投入最小产出最快的。但是如果是搭建一个SAAS平台或者千万量级的IM，XMPP就不是最优的选择了。当然这是一家之言，国内外目前商业化的IM SAAS平台有好几家都是基于XMPP实现的，这个大家可以自行Google。</p><hr><h3 id="MQTT"><a href="#MQTT" class="headerlink" title="MQTT"></a>MQTT</h3><p>MQTT是轻量级基于代理的发布/订阅的消息传输协议，它的最大特点就是协议开销非常小，伴随着的就是协议简单（40多页）、网络带宽要求极低和移动设备省电。有幸接触到该协议是笔者在开发Android推送系统时，对它进行了较细致的研究，虽然最终方案中没有使用该协议，但是自己定制的私有协议也参考了很多MQTT的设计。MQTT整个协议的组成，可以分为三个部分：</p><ol><li><strong>固定头部</strong>：通用消息数据包格式</li><li><strong>可变头部</strong>：特定消息数据包格式</li><li><strong>消息体</strong>：有效载荷</li></ol><h4 id="固定头部"><a href="#固定头部" class="headerlink" title="固定头部"></a>固定头部</h4><p>每个MQTT命令消息的消息头部都包含一个固定头部，固定头部的格式如下：</p><p><img src="/images/mqtt-fixed-header.jpg" alt="fixed header"></p><p><strong>Byte 1</strong><br>消息类型和标志字段</p><p><strong>Byte 2</strong><br>剩余长度字段（至少1个字节，最多4个字节），采用big-endian模式存储</p><p><strong>Message Type</strong></p><p><img src="/images/mqtt-fixed-header-message1.jpg" alt="mqtt-fixed-header-message1"></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">0：保留</span><br><span class="line">1：客户端请求连接服务器</span><br><span class="line">2：连接确认</span><br><span class="line">3：发布消息</span><br><span class="line">4：发布确认</span><br><span class="line">5：发布接收（有保证的交付第1部分）</span><br><span class="line">6：发布释放（有保证的交付第2部分）</span><br><span class="line">7：发布完成（有保证的交付第3部分）</span><br><span class="line">8：客户端订阅请求</span><br><span class="line">9：订阅确认</span><br><span class="line">10：客户端取消订阅请求</span><br><span class="line">11：取消订阅确认</span><br><span class="line">12：PING请求</span><br><span class="line">13：PING回复</span><br><span class="line">14：客户端断开连接</span><br><span class="line">15：保留</span><br></pre></td></tr></table></figure><h4 id="DUP-Duplicate-delivery"><a href="#DUP-Duplicate-delivery" class="headerlink" title="DUP(Duplicate delivery)"></a>DUP(Duplicate delivery)</h4><p>保证消息可靠传输，默认为0，只占用一个bit，表示是否第一次发送，它不能用于检测消息重复发送。只适用于客户端或服务器端尝试重发PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE消息，注意需要满足以下条件：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">QoS &gt; 0</span><br><span class="line">即消息需要回复确认</span><br></pre></td></tr></table></figure><p>此时，在可变头部需要包含消息ID。当值为1时，表示当前消息先前已经被传送过。</p><h4 id="Qos-Quality-of-Service"><a href="#Qos-Quality-of-Service" class="headerlink" title="Qos(Quality of Service)"></a>Qos(Quality of Service)</h4><p>该标志位标明 PUBLISH 消息的交付质量级别:</p><p><img src="/images/mqtt-qos.jpg" alt="mqtt-fixed-header-message1"></p><h4 id="RETAIN"><a href="#RETAIN" class="headerlink" title="RETAIN"></a>RETAIN</h4><p>仅针对PUBLISH消息。不同值，不同含义：</p><p>1：表示发送的消息需要一直持久保存（不受服务器重启影响），不但要发送给当前的订阅者，并且以后新来的订阅了此Topic name的订阅者会马上得到推送。</p><p>备注：新来乍到的订阅者，只会取出最新的一个RETAIN flag = 1的消息推送。</p><p>0：仅仅为当前订阅者推送此消息。</p><p>假如服务器收到一个空消息体(zero-length payload)、RETAIN = 1、已存在Topic name的PUBLISH消息，服务器可以删除掉对应的已被持久化的PUBLISH消息。</p><h4 id="Remaining-Length"><a href="#Remaining-Length" class="headerlink" title="Remaining Length"></a>Remaining Length</h4><p>这个字节包含当前消息的剩余部分，包括变量头部和负载的数据。</p><p>可变长度的编码方式使用一个单独的字节使消息可以达到127字节的长度上限。协议限制最多4个字节，这样程序可以发送最大256Ｍ的消息。</p><p>上面便是最核心的固定头部的内容，至于可变头部和消息体可以自己查询资料，目前有很多公司在使用MQTT实现Android的推送，但是目前笔者暂时不知道谁家的IM在使用它。</p><h3 id="私有协议"><a href="#私有协议" class="headerlink" title="私有协议"></a>私有协议</h3><p>一万人眼中就有一万个哈姆雷特，同样的一万人眼中就有一万个私有协议。应用场景、设计风格，都会导致协议的设计千奇百怪。例如：数据量传输大的场景，压缩方案可能也被设计到协议中，因为不同的环境可能用到不同的压缩方式；传输质量，我们可能就默认某一个级别，可能就从协议中移除，具体的设计得靠经验和应用场景来设计。</p><hr><h3 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h3><p>做了好几个系统，我将我喜欢使用的一套架构抛出来供大家探讨。<br><img src="/images/architecture.png" alt="mqtt-fixed-header-message1"></p><ul><li><strong>CM-*</strong>:Connection Manager，可以分为WebSocket和Tcp两种承载方式。</li><li><strong>SM</strong>:Session Manager。</li><li><strong>Web</strong>:Rest接口，HTTP承载。历史消息，好友关系，个人信息管理等。</li></ul><p>一套很简单的架构，<strong>CM</strong>只负责链路的管理，链路和用户ID的关系维护在Redis中，<strong>SM</strong>负责业务逻辑和消息路由。<strong>CM</strong>和<strong>SM</strong>内部通过RPC调用，<strong>CM</strong>和<strong>SM</strong>内部全部采用事件驱动的方式，全部采用异步的方式。任何一个模块都可以水平扩展，并且<strong>SM</strong>如果达到非常复杂的地步，还可以拆分。最终的压力基本就到了Redis和Mysql，这些高可用和高并发的方案，已经非常成熟，就不用多说了。</p><p>下图是登录流程和消息发送流程</p><p><img src="/images/login.png" alt="login"></p><p><img src="/images/message.png" alt="message"></p><blockquote><p>鉴于笔者经验，开发的IM最多承载用户数也就百万级别，所以架构上或者设计方案不一定完美，仅供参考！</p></blockquote><p><strong>注意事项</strong></p><ol><li>CM一定要采用多队列网卡，否者会出现服务器的一个CPU 100%，而别的CPU却很空闲，从而导致系统吞吐量上不去。因为单队列网卡的I/O中断都被分配到了一个CPU核上，大量数据包到来时，单个CPU核无法全部处理，导致LVS不断丢包连接中断。</li></ol>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;前前后后做的IM和推送系统已经有好几个了，一直都想好好总结下，因此就有了这篇文章。在我刚学编程的那会儿，觉得网络通信是一个很牛逼和门槛很高的一门技术，但是随着开源技术的发展和互联网知识的共享，现在要写出高质量的网络通信程序已经变得容易多了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;只要谈通讯肯定绕不开协议，鉴于本人经验下面只谈本人撸过的三种协议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;XMPP&lt;/li&gt;
&lt;li&gt;MQTT&lt;/li&gt;
&lt;li&gt;私有协议&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="IM" scheme="https://www.yangguo.info/tags/IM/"/>
    
  </entry>
  
  <entry>
    <title>Java agent笔记</title>
    <link href="https://www.yangguo.info/2015/07/18/JavaAgent%E7%9A%84%E4%B8%80%E7%82%B9%E6%80%9D%E8%B7%AF/"/>
    <id>https://www.yangguo.info/2015/07/18/JavaAgent的一点思路/</id>
    <published>2015-07-18T13:42:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近对Java的<strong>Profiling</strong>和<strong>Debugging</strong>非常感兴趣，特别是对线上问题的定位方案进行了较深的调研和分析。因为在<strong>SOA</strong>架构体系中线上问题经常在测试环境不能复现，所以问题的定位具有非常大的挑战。我将业界定位问题的工具和方案都大概的研究了一遍，不论是<strong><a href="http://www.ej-technologies.com/products/jprofiler/overview.html" target="_blank" rel="noopener">JProfiler</a></strong>、<strong><a href="http://www.yourkit.com/" target="_blank" rel="noopener">YourKit</a></strong>、<strong><a href>BTrace</a></strong>，还是更底层的<strong><a href="http://openjdk.java.net/groups/hotspot/docs/Serviceability.html" target="_blank" rel="noopener">Serviceability技术</a></strong>……都广泛用到了<code>Agent</code>技术。</p></blockquote><a id="more"></a><p>在开始之前有必要将HotSpot JVM的<strong>Serviceability</strong>做个简单的说明。<strong>Serviceability</strong>可以翻译为<strong>服务性</strong>，它可以理解为一种能力。这种能力表现在HotSpot虚拟机具备跟踪调试Java进程的能力，可以针对故障分析和隔离。当然作为一名Java程序员，绝大多数时候我们都不需要关注到此层面，但是当我们需要做一个更加深层次的调试工作时我们就绕不开Serviceability，具体的技术有如下几项。</p><ol><li><strong>The Serviceability Agent(SA)</strong>.Sun私有的一个组件，它可以帮助开发者调试Hotspot。</li><li><strong>jvmstat performance counters</strong>.HotSpot通过Sun私有的共享内存机制实现的性能计数器，它是公开给外部给外部进程的。它们也称为<strong>perfdata</strong>。</li><li><strong>The Java Virtual Machine Tool Interface (JVM TI)</strong>.它是一套标准的C接口，是<a href="http://jcp.org/en/jsr/detail?id=163" target="_blank" rel="noopener"> JSR 163 - JavaTM Platform Profiling Architecture </a>参考实现。JVMTI提供了可用于 debug和profiler的接口；同时，在Java 5/6 中，虚拟机接口也增加了监听（Monitoring），线程分析（Thread analysis）以及覆盖率分析（Coverage Analysis）等功能。JVMTI并不一定在所有的Java虚拟机上都有实现，不同的虚拟机的实现也不尽相同。不过在一些主流的虚拟机中，比如 Sun 和 IBM，以及一些开源的如 Apache Harmony DRLVM 中，都提供了标准 JVMTI 实现。</li><li><strong>The Monitoring and Management interface</strong>.Sun的私有实现，它能够实现对HotSpot的监控和管理。</li><li><strong>Dynamic Attach</strong>.Sun的私有机制，它允许外部进程在HotSpot中启动一个agent线程，该agent可以将该HotSPot的信息发送给外部进程。</li><li><strong>DTrace</strong>.全称Dynamic Tracing，也称为动态跟踪，是由Sun™开发的一个用来在生产和试验性生产系统上找出系统瓶颈的工具，可以对内核(kernel)和用户应用程序(user application)进行动态跟踪并且对系统运行不构成任何危险的技术。在任何情况下它都不是一个调试工具，而是一个实时系统分析寻找出性能及其他问题的工具。DTrace是个特别好的分析工具，带有大量的帮助诊断系统问题的特性。还可以使用预先写好的脚本利用它的功能。用户也可以通过使用 DTrace D语言创建他们自己定制的分析工具，以满足特定的需求。除Solaris系列以外，Dtrace已先后被移植到FreeBSD、NetBSD及Max OS X等操作系统上</li><li><strong>pstack support</strong>.pstack是从Solaris发源的实用程序,可以查看core dump文件，调试进程，查看线程运行情况等等。现在，pstack工具也移植到了Linux系统中，比如Red Hat Linux系统、Ubuntu Linux系统等等。HotSpot允许pstack显示Java堆栈帧。</li></ol><p>在这一系列的技术中，<strong>SA</strong>和<strong>JVM TI</strong>容易产生混淆，<strong>SA</strong>主要是给JVM开发人员调试虚拟机用的，<strong>JVM TI</strong>是为Java应用层开发者而设计的，它是标准API，主要用来开发各种调试和分析工具。也就是说<strong>SA</strong>更底层，如果不触碰JVM虚拟机底层开发，其实绝大多数时候是用不到的。当然也不是说，我们平常完全不会用到<strong>SA</strong>的功能。比如，<strong>jstack -F/m</strong>就会使用到<strong>SA</strong>，我们平常不加<strong>-F/-m</strong>的时候默认走的是<strong>Instrumentation</strong>的<strong>attach</strong>操作。<strong>jmap</strong>中的部分功能也是<strong>SA</strong>实现的，具体的没有详细研究。<strong>Attach</strong>是进程内操作，<strong>SA</strong>是进程外操作，这便是他们的区别之处。大家可能碰见过这样的例子，就是Java进程hung住了，使用jstack是拿不到threaddump的，这个时候只有加<strong>-F</strong>才可以拿到，并且拿到的dump数据跟不加<strong>-F</strong>拿到的数据是有差别的。那是因为attach进JVM之后，要拿到threaddump，需要JVM执行操作才行，但是这个时候JVM已经不能响应任何操作了，所以拿不到。但是进程外就不一样了，它是以一个观察者的方式去查看目标进程，所以能够拿到想要的信息。</p><p>虽然<strong>Serviceability</strong>很强大，但是本篇文章真正想讲的是<strong>Instrumentation</strong>特性。虽然<strong>JDK5</strong>中就已经有了<code>premain</code>，但是真正具有划时代意义的还是<strong>JDK6</strong>中的<code>agentmain</code>。因为当不容易重现的问题出现时，你可以不用重启应用，便可以对应用进行问题定位和调试，当然目前只有<strong>HotSpot</strong>和<strong>JRockit</strong>虚拟机支持。下面给一个通过环绕织入来实现获取方法执行时间的例子。</p><p><strong>1.</strong>首先来一个注解，它是用在方法上，表示该方法我需要织入代码来获取执行时间。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">package info.yangguo.demo.attch_api.test4;</span><br><span class="line"></span><br><span class="line">public @interface Point &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2.</strong>来一个被织入的测试代码，为了方便测试，classpath动态加载<strong><a href="http://www.javassist.org/" target="_blank" rel="noopener">javassist</a></strong>，因为该类库在我后面类的transform中使用到了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.yangguo.demo.attch_api.test4;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"><span class="keyword">import</span> java.net.URL;</span><br><span class="line"><span class="keyword">import</span> java.net.URLClassLoader;</span><br><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestExample</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Random random = <span class="keyword">new</span> Random();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Point</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doSleep</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">15000</span>);</span><br><span class="line">            System.out.println(<span class="string">"*"</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Point</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doTask</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(random.nextInt(<span class="number">1500</span>));</span><br><span class="line">            System.out.println(<span class="string">"."</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Point</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">doWork</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; random.nextInt(<span class="number">12</span>); i++) &#123;</span><br><span class="line">            doTask();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 为了减少设置classpath的做法，具体的类库路径需要自己设置</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        ExtClasspathLoader.loadClasspath(<span class="string">"/Users/yangguo/.gradle/caches/modules-2/files-2.1/org.javassist/javassist/3.20.0-GA/a9cbcdfb7e9f86fbc74d3afae65f2248bfbf82a0"</span>);</span><br><span class="line">        TestExample test = <span class="keyword">new</span> TestExample();</span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            test.doWork();</span><br><span class="line">            test.doSleep();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据properties中配置的路径把jar和配置文件加载到classpath中。</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@author</span> guo.yang</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ExtClasspathLoader</span> </span>&#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * URLClassLoader的addURL方法</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> Method addURL = initAddMethod();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader();</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 初始化addUrl 方法.</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@return</span> 可访问addUrl方法的Method对象</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Method <span class="title">initAddMethod</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Method add = URLClassLoader.class.getDeclaredMethod(<span class="string">"addURL"</span>, <span class="keyword">new</span> Class[]&#123;URL.class&#125;);</span><br><span class="line">                add.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">                <span class="keyword">return</span> add;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loadClasspath</span><span class="params">(String filepath)</span> </span>&#123;</span><br><span class="line">            File file = <span class="keyword">new</span> File(filepath);</span><br><span class="line">            loopFiles(file);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loadResourceDir</span><span class="params">(String filepath)</span> </span>&#123;</span><br><span class="line">            File file = <span class="keyword">new</span> File(filepath);</span><br><span class="line">            loopDirs(file);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 循环遍历目录，找出所有的资源路径。</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> file 当前遍历文件</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loopDirs</span><span class="params">(File file)</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 资源文件只加载路径</span></span><br><span class="line">            <span class="keyword">if</span> (file.isDirectory()) &#123;</span><br><span class="line">                addURL(file);</span><br><span class="line">                File[] tmps = file.listFiles();</span><br><span class="line">                <span class="keyword">for</span> (File tmp : tmps) &#123;</span><br><span class="line">                    loopDirs(tmp);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 循环遍历目录，找出所有的jar包。</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> file 当前遍历文件</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loopFiles</span><span class="params">(File file)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (file.isDirectory()) &#123;</span><br><span class="line">                File[] tmps = file.listFiles();</span><br><span class="line">                <span class="keyword">for</span> (File tmp : tmps) &#123;</span><br><span class="line">                    loopFiles(tmp);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (file.getAbsolutePath().endsWith(<span class="string">".jar"</span>) || file.getAbsolutePath().endsWith(<span class="string">".zip"</span>)) &#123;</span><br><span class="line">                    addURL(file);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 通过filepath加载文件到classpath。</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> file 文件路径</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@return</span> URL</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@throws</span> Exception 异常</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">addURL</span><span class="params">(File file)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                addURL.invoke(classloader, <span class="keyword">new</span> Object[]&#123;file.toURI().toURL()&#125;);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3.</strong>当然少不了最重要的<strong>Transform</strong>，在这里面我使用<strong>javassist</strong>动态修改了字节码，实现了对目标方法的环绕织入。更多更强大的功能就得各位的具体需求了，比如我们还可以在此处加一个监控的report功能，从而实现实时监控。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.yangguo.demo.attch_api.test4;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javassist.ByteArrayClassPath;</span><br><span class="line"><span class="keyword">import</span> javassist.ClassPool;</span><br><span class="line"><span class="keyword">import</span> javassist.CtClass;</span><br><span class="line"><span class="keyword">import</span> javassist.CtMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.instrument.ClassFileTransformer;</span><br><span class="line"><span class="keyword">import</span> java.lang.instrument.IllegalClassFormatException;</span><br><span class="line"><span class="keyword">import</span> java.security.ProtectionDomain;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Transformer</span> <span class="keyword">implements</span> <span class="title">ClassFileTransformer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> ClassPool classPool;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Transformer</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        classPool = <span class="keyword">new</span> ClassPool();</span><br><span class="line">        classPool.appendSystemPath();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            classPool.appendPathList(System.getProperty(<span class="string">"java.class.path"</span>));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            System.out.println(<span class="string">"异常:"</span> + e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">byte</span>[] transform(ClassLoader loader, String fullyQualifiedClassName, Class&lt;?&gt; classBeingRedefined,</span><br><span class="line">                            ProtectionDomain protectionDomain, <span class="keyword">byte</span>[] classBytes) <span class="keyword">throws</span> IllegalClassFormatException &#123;</span><br><span class="line">        String className = fullyQualifiedClassName.replace(<span class="string">"/"</span>, <span class="string">"."</span>);</span><br><span class="line"></span><br><span class="line">        classPool.appendClassPath(<span class="keyword">new</span> ByteArrayClassPath(className, classBytes));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            CtClass ctClass = classPool.get(className);</span><br><span class="line">            <span class="keyword">if</span> (ctClass.isFrozen()) &#123;</span><br><span class="line">                System.out.println(<span class="string">"Skip class "</span> + className + <span class="string">": is frozen"</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (ctClass.isPrimitive() || ctClass.isArray() || ctClass.isAnnotation()</span><br><span class="line">                    || ctClass.isEnum() || ctClass.isInterface()) &#123;</span><br><span class="line">                System.out.println(<span class="string">"Skip class "</span> + className + <span class="string">": not a class"</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">boolean</span> isClassModified = <span class="keyword">false</span>;</span><br><span class="line">            <span class="keyword">for</span> (CtMethod method : ctClass.getDeclaredMethods()) &#123;</span><br><span class="line">                <span class="comment">// if method is annotated, add the code to measure the time</span></span><br><span class="line">                <span class="keyword">if</span> (method.hasAnnotation(Point.class)) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (method.getMethodInfo().getCodeAttribute() == <span class="keyword">null</span>) &#123;</span><br><span class="line">                        System.out.println(<span class="string">"Skip method "</span> + method.getLongName());</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    System.out.println(<span class="string">"Instrumenting method "</span> + method.getLongName());</span><br><span class="line">                    <span class="comment">//此处实现了一个AOP的环绕织入，在我们常规的需求中，可以在新增监控的report逻辑，</span></span><br><span class="line">                    <span class="comment">//如果需要加载第三方的jar，可以通过动态加载classpath来实现</span></span><br><span class="line"></span><br><span class="line">                    method.addLocalVariable(<span class="string">"__metricStartTime"</span>, CtClass.longType);</span><br><span class="line">                    method.insertBefore(<span class="string">"__metricStartTime = System.currentTimeMillis();"</span>);</span><br><span class="line">                    method.insertAfter(<span class="string">"System.out.println( System.currentTimeMillis() - __metricStartTime);"</span>);</span><br><span class="line">                    isClassModified = <span class="keyword">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (!isClassModified) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> ctClass.toBytecode();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            System.out.println(<span class="string">"Skip class : "</span> + className + <span class="string">",Error Message:"</span> + e.getMessage());</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>4.</strong> <strong>Agent</strong>代码主要是设置<code>transformer</code>和<code>retransformClasses</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.yangguo.demo.attch_api.test4;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.instrument.Instrumentation;</span><br><span class="line"><span class="keyword">import</span> java.lang.instrument.UnmodifiableClassException;</span><br><span class="line"><span class="keyword">import</span> java.lang.management.ManagementFactory;</span><br><span class="line"><span class="keyword">import</span> java.lang.management.RuntimeMXBean;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Agent</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">agentmain</span><span class="params">(String agentArguments, Instrumentation instrumentation)</span> <span class="keyword">throws</span> UnmodifiableClassException </span>&#123;</span><br><span class="line">        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();</span><br><span class="line">        System.out.println(<span class="string">"Runtime:"</span> + runtimeMxBean.getName() + <span class="string">" : "</span> + runtimeMxBean.getInputArguments());</span><br><span class="line">        System.out.println(<span class="string">"Starting agent with arguments "</span> + agentArguments);</span><br><span class="line"></span><br><span class="line">        instrumentation.addTransformer(<span class="keyword">new</span> Transformer(),<span class="keyword">true</span>);</span><br><span class="line">        instrumentation.retransformClasses(TestExample.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>5.</strong>为了方便加了一个自动打包的类，自动生成agent jar，主要是对<strong>Transformer</strong>、<strong>Agent</strong>打包，并设置<strong>Manifest</strong>。这样做的好处是可以通过代码自动化，方便二次开发成内部工具。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.yangguo.demo.attch_api.test4;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.tools.JavaCompiler;</span><br><span class="line"><span class="keyword">import</span> javax.tools.ToolProvider;</span><br><span class="line"><span class="keyword">import</span> java.io.*;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.jar.Attributes;</span><br><span class="line"><span class="keyword">import</span> java.util.jar.JarEntry;</span><br><span class="line"><span class="keyword">import</span> java.util.jar.JarOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.util.jar.Manifest;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:15/7/8</span></span><br><span class="line"><span class="comment"> * Time:上午10:26</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * Description:</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JavacCompileAndJar</span> </span>&#123;</span><br><span class="line">    String javaSourcePath;</span><br><span class="line">    String javaClassPath;</span><br><span class="line">    String targetPath;</span><br><span class="line">    String jarClassPath;</span><br><span class="line">    HashMap&lt;Object, Object&gt; attributes;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">JavacCompileAndJar</span><span class="params">(String javaSourcePath, String javaClassPath, String jarClassPath, String targetPath, HashMap attributes)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.javaSourcePath = javaSourcePath;</span><br><span class="line">        <span class="keyword">this</span>.javaClassPath = javaClassPath;</span><br><span class="line">        <span class="keyword">this</span>.targetPath = targetPath;</span><br><span class="line">        <span class="keyword">this</span>.jarClassPath = jarClassPath;</span><br><span class="line">        <span class="keyword">this</span>.attributes = attributes;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">complier</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"*** --&gt; 开始编译java源代码..."</span>);</span><br><span class="line"></span><br><span class="line">        File javaclassDir = <span class="keyword">new</span> File(javaClassPath);</span><br><span class="line">        <span class="keyword">if</span> (!javaclassDir.exists()) &#123;</span><br><span class="line">            javaclassDir.mkdirs();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        List&lt;String&gt; javaSourceList = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">        getFileList(<span class="keyword">new</span> File(javaSourcePath), javaSourceList);</span><br><span class="line"></span><br><span class="line">        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();</span><br><span class="line">        <span class="keyword">int</span> result = -<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; javaSourceList.size(); i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (javaSourceList.get(i).endsWith(<span class="string">"java"</span>)) &#123;</span><br><span class="line">                result = javaCompiler.run(<span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="string">"-d"</span>, javaClassPath, javaSourceList.get(i));</span><br><span class="line">                System.out.println(result == <span class="number">0</span> ? <span class="string">"*** 编译成功 : "</span> + javaSourceList.get(i) : <span class="string">"### 编译失败 : "</span> + javaSourceList.get(i));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">"*** --&gt; java源代码编译完成。"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">getFileList</span><span class="params">(File file, List&lt;String&gt; fileList)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (file.isDirectory()) &#123;</span><br><span class="line">            File[] files = file.listFiles();</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; files.length; i++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (files[i].isDirectory()) &#123;</span><br><span class="line">                    getFileList(files[i], fileList);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    fileList.add(files[i].getPath());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">generateJar</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"*** --&gt; 开始生成jar包..."</span>);</span><br><span class="line">        String targetDirPath = targetPath.substring(<span class="number">0</span>, targetPath.lastIndexOf(<span class="string">"/"</span>));</span><br><span class="line">        File targetDir = <span class="keyword">new</span> File(targetDirPath);</span><br><span class="line">        <span class="keyword">if</span> (!targetDir.exists()) &#123;</span><br><span class="line">            targetDir.mkdirs();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Manifest manifest = <span class="keyword">new</span> Manifest();</span><br><span class="line">        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, <span class="string">"1.0"</span>);</span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;Object, Object&gt; attribute : attributes.entrySet()) &#123;</span><br><span class="line">            manifest.getMainAttributes().put(attribute.getKey(), attribute.getValue());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        JarOutputStream target = <span class="keyword">new</span> JarOutputStream(<span class="keyword">new</span> FileOutputStream(targetPath), manifest);</span><br><span class="line">        writeClassFile(<span class="keyword">new</span> File(jarClassPath), target);</span><br><span class="line">        target.close();</span><br><span class="line">        System.out.println(<span class="string">"*** --&gt; jar包生成完毕。"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">writeClassFile</span><span class="params">(File source, JarOutputStream target)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        BufferedInputStream in = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (source.isDirectory()) &#123;</span><br><span class="line">                String name = source.getPath().replace(<span class="string">"\\"</span>, <span class="string">"/"</span>);</span><br><span class="line">                <span class="keyword">if</span> (!name.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (!name.endsWith(<span class="string">"/"</span>)) &#123;</span><br><span class="line">                        name += <span class="string">"/"</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    name = name.substring(jarClassPath.length());</span><br><span class="line">                    JarEntry entry = <span class="keyword">new</span> JarEntry(name);</span><br><span class="line">                    entry.setTime(source.lastModified());</span><br><span class="line">                    target.putNextEntry(entry);</span><br><span class="line">                    target.closeEntry();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">for</span> (File nestedFile : source.listFiles())</span><br><span class="line">                    writeClassFile(nestedFile, target);</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            String middleName = source.getPath().replace(<span class="string">"\\"</span>, <span class="string">"/"</span>).substring(javaClassPath.length());</span><br><span class="line">            JarEntry entry = <span class="keyword">new</span> JarEntry(middleName);</span><br><span class="line">            entry.setTime(source.lastModified());</span><br><span class="line">            target.putNextEntry(entry);</span><br><span class="line">            in = <span class="keyword">new</span> BufferedInputStream(<span class="keyword">new</span> FileInputStream(source));</span><br><span class="line"></span><br><span class="line">            <span class="keyword">byte</span>[] buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line">            <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">                <span class="keyword">int</span> count = in.read(buffer);</span><br><span class="line">                <span class="keyword">if</span> (count == -<span class="number">1</span>)</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                target.write(buffer, <span class="number">0</span>, count);</span><br><span class="line">            &#125;</span><br><span class="line">            target.closeEntry();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (in != <span class="keyword">null</span>)</span><br><span class="line">                in.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, InterruptedException </span>&#123;</span><br><span class="line">        String javaSourcePath = <span class="string">"/Users/yangguo/work/code/demo/src/main/java/info/yangguo/demo/attach_api/test4"</span>;</span><br><span class="line">        String javaClassPath = <span class="string">"/Users/yangguo/Downloads/demo/classes/info/yangguo/demo/attach_api/test4"</span>;</span><br><span class="line">        String jarClassPath = <span class="string">"/Users/yangguo/Downloads/demo/classes"</span>;</span><br><span class="line">        String targetPath = <span class="string">"/Users/yangguo/Downloads/demo/target/libs/demo-1.0-SNAPSHOT-fat.jar"</span>;</span><br><span class="line"></span><br><span class="line">        HashMap&lt;Object, Object&gt; attributes = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        Attributes.Name agentClass = <span class="keyword">new</span> Attributes.Name(<span class="string">"Agent-Class"</span>);</span><br><span class="line">        attributes.put(agentClass, <span class="string">"info.yangguo.demo.attch_api.test4.Agent"</span>);</span><br><span class="line">        Attributes.Name canRedineClasses = <span class="keyword">new</span> Attributes.Name(<span class="string">"Can-Redefine-Classes"</span>);</span><br><span class="line">        attributes.put(canRedineClasses, <span class="string">"true"</span>);</span><br><span class="line">        Attributes.Name canRetransformClasses = <span class="keyword">new</span> Attributes.Name(<span class="string">"Can-Retransform-Classes"</span>);</span><br><span class="line">        attributes.put(canRetransformClasses, <span class="string">"true"</span>);</span><br><span class="line"></span><br><span class="line">        JavacCompileAndJar javacCompileAndJar = <span class="keyword">new</span> JavacCompileAndJar(javaSourcePath, javaClassPath, jarClassPath, targetPath, attributes);</span><br><span class="line">        javacCompileAndJar.complier();</span><br><span class="line">        javacCompileAndJar.generateJar();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>6.</strong>最后便是<strong>Instrumentation</strong>的的入口类，将<code>Agent</code>attach到前面的测试代码中去。代码中我将机器上所有的Java进程给遍历一遍，选择自己感兴趣的进程（TestExample）<code>attach</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.yangguo.demo.attch_api.test4;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:15/6/29</span></span><br><span class="line"><span class="comment"> * Time:下午10:06</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * Description:</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.sun.tools.attach.VirtualMachine;</span><br><span class="line"><span class="keyword">import</span> com.sun.tools.attach.VirtualMachineDescriptor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AttachMain</span> <span class="keyword">extends</span> <span class="title">Thread</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String jar;</span><br><span class="line"></span><br><span class="line">    AttachMain(String attachJar) &#123;</span><br><span class="line">        jar = attachJar;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        VirtualMachine vm = <span class="keyword">null</span>;</span><br><span class="line">        List&lt;VirtualMachineDescriptor&gt; listAfter = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">int</span> count = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">                listAfter = VirtualMachine.list();</span><br><span class="line">                <span class="keyword">for</span> (VirtualMachineDescriptor vmd : listAfter) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (vmd.displayName().contains(<span class="string">"TestExample"</span>)) &#123;</span><br><span class="line">                        <span class="comment">// 如果 VM 有增加，我们就认为是被监控的 VM 启动了</span></span><br><span class="line">                        <span class="comment">// 这时，我们开始监控这个 VM</span></span><br><span class="line">                        vm = VirtualMachine.attach(vmd);</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">                count++;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">null</span> != vm || count &gt;= <span class="number">10</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            vm.loadAgent(jar);</span><br><span class="line">            vm.detach();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">new</span> AttachMain(<span class="string">"/Users/yangguo/Downloads/demo/target/libs/demo-1.0-SNAPSHOT-fat.jar"</span>).start();</span><br><span class="line">        Thread.sleep(<span class="number">10000000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近对Java的&lt;strong&gt;Profiling&lt;/strong&gt;和&lt;strong&gt;Debugging&lt;/strong&gt;非常感兴趣，特别是对线上问题的定位方案进行了较深的调研和分析。因为在&lt;strong&gt;SOA&lt;/strong&gt;架构体系中线上问题经常在测试环境不能复现，所以问题的定位具有非常大的挑战。我将业界定位问题的工具和方案都大概的研究了一遍，不论是&lt;strong&gt;&lt;a href=&quot;http://www.ej-technologies.com/products/jprofiler/overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JProfiler&lt;/a&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;a href=&quot;http://www.yourkit.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;YourKit&lt;/a&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;a href&gt;BTrace&lt;/a&gt;&lt;/strong&gt;，还是更底层的&lt;strong&gt;&lt;a href=&quot;http://openjdk.java.net/groups/hotspot/docs/Serviceability.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Serviceability技术&lt;/a&gt;&lt;/strong&gt;……都广泛用到了&lt;code&gt;Agent&lt;/code&gt;技术。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>外部HTTP服务访问慢的Case</title>
    <link href="https://www.yangguo.info/2015/06/30/%E5%A4%96%E9%83%A8HTTP%E6%9C%8D%E5%8A%A1%E8%AE%BF%E9%97%AE%E6%85%A2%E7%9A%84Case/"/>
    <id>https://www.yangguo.info/2015/06/30/外部HTTP服务访问慢的Case/</id>
    <published>2015-06-30T10:32:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="问题现象"><a href="#问题现象" class="headerlink" title="问题现象"></a>问题现象</h3><p>最近由于业务需求，重写了一套访问中航信IBE+的代理层。由于机票业务分为国内和国际两部分，这两部分都需要访问IBE+，之前是各自为战，各自都有一套访问逻辑。这样做的后果就是在流控(<code>IBE+对QPS有一定的限制</code>)和接口管理方面得不到很好的控制，因此才写了这样的一个代理层。这样的设计其实没有任何问题，在微服务和SOA大行其道的今天这都是主流设计思路。该系统很快便上线了，但是很快我就发现IBE+的服务访问都非常慢，因为之前我没有涉及到这块，我就问了下之前负责这块的同事，他们说的确比较慢，我也就没有太上心。突然有天我打开生产环境的监控，我便发现了问题，大概30秒左右便有一次非常慢的操作。关于30秒的问题，之前我通过tcpdump抓包分析过，以为是Http Basic鉴权导致的，在新写的代理层中已经对此处进行了优化，但是结果看来不是很理想，然后便有了这个Case。</p><a id="more"></a><hr><h3 id="问题定位"><a href="#问题定位" class="headerlink" title="问题定位"></a>问题定位</h3><h4 id="猜测-初步验证"><a href="#猜测-初步验证" class="headerlink" title="猜测+初步验证"></a>猜测+初步验证</h4><p>30秒左右就会慢一次，思来想去就只有DNS的问题，因为Java的DNS默认缓存大概就是30秒。然后我迅速写了如下的一段测试代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.net.Inet4Address;</span><br><span class="line"><span class="keyword">import</span> java.net.InetAddress;</span><br><span class="line"><span class="keyword">import</span> java.net.UnknownHostException;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:15/6/18</span></span><br><span class="line"><span class="comment"> * Time:下午2:55</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * Description:DNS解析速度测试</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String args[])</span> <span class="keyword">throws</span> UnknownHostException, InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            String host = <span class="string">"agibe.travelsky.com"</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">long</span> begin1 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            Inet4Address.getByName(host);</span><br><span class="line">            <span class="keyword">long</span> end1 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            System.out.println(end1 - begin1);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">            <span class="keyword">long</span> begin2 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            InetAddress.getAllByName(host);</span><br><span class="line">            <span class="keyword">long</span> end2 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            System.out.println(end2 - begin2);</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">"----"</span>);</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>*<span class="number">5</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>放到生产环境的服务器一测，果然每个30秒便巨慢一次，大概每次解析在5秒钟左右。网上还有一套通过反射来获取Java DNS缓存时间的测试代码，代码非常简单如下所示：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.net.InetAddress;</span><br><span class="line"><span class="keyword">import</span> java.text.SimpleDateFormat;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:15/5/7</span></span><br><span class="line"><span class="comment"> * Time:下午5:10</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * Description:</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * dns缓存时间测试</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DnsTester</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"start loop\n\n"</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">30</span>; ++i) &#123;</span><br><span class="line">            Date d = <span class="keyword">new</span> Date();</span><br><span class="line">            SimpleDateFormat sdf = <span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>);</span><br><span class="line">            System.out.println(<span class="string">"current time:"</span> + sdf.format(d));</span><br><span class="line">            InetAddress addr1 = InetAddress.getByName(<span class="string">"www.baidu.com"</span>);</span><br><span class="line">            String addressCache = <span class="string">"addressCache"</span>;</span><br><span class="line">            System.out.println(addressCache);</span><br><span class="line">            printDNSCache(addressCache);</span><br><span class="line">            System.out.println(<span class="string">"getHostAddress:"</span> + addr1.getHostAddress());</span><br><span class="line">            System.out.println(<span class="string">"*******************************************"</span>);</span><br><span class="line">            System.out.println(<span class="string">"\n"</span>);</span><br><span class="line">            java.lang.Thread.sleep(<span class="number">10000</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"end loop"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">printDNSCache</span><span class="params">(String cacheName)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Class&lt;InetAddress&gt; klass = InetAddress.class;</span><br><span class="line">        Field acf = klass.getDeclaredField(cacheName);</span><br><span class="line">        acf.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">        Object addressCache = acf.get(<span class="keyword">null</span>);</span><br><span class="line">        Class cacheKlass = addressCache.getClass();</span><br><span class="line">        Field cf = cacheKlass.getDeclaredField(<span class="string">"cache"</span>);</span><br><span class="line">        cf.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">        Map&lt;String, Object&gt; cache = (Map&lt;String, Object&gt;) cf.get(addressCache);</span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;String, Object&gt; hi : cache.entrySet()) &#123;</span><br><span class="line">            Object cacheEntry = hi.getValue();</span><br><span class="line">            Class cacheEntryKlass = cacheEntry.getClass();</span><br><span class="line">            Field expf = cacheEntryKlass.getDeclaredField(<span class="string">"expiration"</span>);</span><br><span class="line">            expf.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">            <span class="keyword">long</span> expires = (Long) expf.get(cacheEntry);</span><br><span class="line"></span><br><span class="line">            System.out.println(hi.getKey() + <span class="string">" "</span> + <span class="keyword">new</span> Date(expires) + <span class="string">" "</span> + hi.getValue());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="深入代码分析-测试"><a href="#深入代码分析-测试" class="headerlink" title="深入代码分析+测试"></a>深入代码分析+测试</h4><p>经过初步的分析和测试，我已经找到问题的所在，现在就要深入代码进行分析看看是否跟我的猜想吻合。由于我的代码使用的是HTTP Client4.3.2，没有配置<strong>DnsResolver</strong>，那么肯定使用的是<strong>SystemDefaultDnsResolver</strong>。这点确认之后，便可以证明第一步的验证逻辑没有偏离方向。然后我便写了如下的一段<strong>Btrace</strong>脚本，看看线上的服务究竟慢在哪里，脚本如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HttpClientSystemDefaultDnsResolverTracer</span> </span>&#123;</span><br><span class="line">    <span class="meta">@TLS</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">long</span> beginTime;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"org.apache.http.impl.conn.SystemDefaultDnsResolver"</span>,</span><br><span class="line">            method = <span class="string">"resolve"</span></span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">traceGetByNameBegin</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        beginTime = timeMillis();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"org.apache.http.impl.conn.SystemDefaultDnsResolver"</span>,</span><br><span class="line">            method = <span class="string">"resolve"</span>,</span><br><span class="line">            location = <span class="meta">@Location</span>(Kind.RETURN)</span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">traceGetByNameEnd</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        println(strcat(strcat(<span class="string">"org.apache.http.impl.conn.SystemDefaultDnsResolver.resolve 耗时:"</span>, str(timeMillis() - beginTime)), <span class="string">"ms"</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然<strong>BTrace</strong>比较安全，但是我将一台机器的流量切走了大部分，只留下很少的部分来做测试，测试下来果然跟我第一步吻合。</p><hr><h3 id="寻找修复方案"><a href="#寻找修复方案" class="headerlink" title="寻找修复方案"></a>寻找修复方案</h3><p>做了上面充足的测试之后，我拿着手上的数据找到复杂网络的同学，开始跟他定位问题。当我看见<strong>/etc/resolv.conf</strong>下面的<strong>nameserver 114.114.114.114</strong>的时候，我心中一震。我马上让他切换到我们的DNS之后，再一运行第一步的测试代码，速度果然马上提升了好几个数量级。我心中一喜，马上让他将机器的DNS切成我们自己的DNS。然后开始开始观察，经过两个小时之后，我发现速度还是没有提升起来。我就开始纳闷了，这是什么问题呢，我们问题不是已经定位到了嘛？我开始怀疑是不是进程没有重启的原因，我马上找了一台没有修改成我们自己DNS的机器将第一个程序跑起来。然后我开始修改DNS，果然修改之后程序的解析速度没有提升，重启进程问题解决。</p><p>看着<strong>QPS</strong>的缓慢爬升以及<strong>Average Response Time</strong>的缓慢下降，心中暗爽。你可能以为这个Case已经结束，的确当时我也是这样认为的。</p><hr><h3 id="DNS解析真的慢？"><a href="#DNS解析真的慢？" class="headerlink" title="DNS解析真的慢？"></a>DNS解析真的慢？</h3><p>大概过了两天，我突然觉得意识到，在这个Case中我遗漏掉了什么。114真的有这慢吗？虽然作为一个经常被吐槽最容易被污染DNS，114的速度我记得还是相当靠谱的啊。Google了一圈，在Stack OverFlow上找到一些讨论，说是IPV6的原因导致的。</p><p>我马上进行了尝试，首先我不指定ipv4，例子如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[root@centos87 ~]<span class="comment"># wget agibe.travelsky.com</span></span><br><span class="line">--2015-07-01 17:56:13--  http://agibe.travelsky.com/</span><br><span class="line">Resolving agibe.travelsky.com... 122.119.122.38</span><br><span class="line">Connecting to agibe.travelsky.com|122.119.122.38|:80... connected.</span><br><span class="line">HTTP request sent, awaiting response... 200 OK</span><br><span class="line">Length: 239 [text/html]</span><br><span class="line">Saving to: “index.html.3”</span><br><span class="line"></span><br><span class="line">100%[=============================================================================================&gt;] 239         --.-K/s   <span class="keyword">in</span> 0s</span><br><span class="line"></span><br><span class="line">2015-07-01 17:56:18 (21.4 MB/s) - “index.html.3” saved [239/239]</span><br></pre></td></tr></table></figure><p>然后我再指定ipv4试了一把，如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">root@centos87 ~]<span class="comment"># wget -4 agibe.travelsky.com</span></span><br><span class="line">--2015-07-01 17:57:20--  http://agibe.travelsky.com/</span><br><span class="line">Resolving agibe.travelsky.com... 122.119.122.38</span><br><span class="line">Connecting to agibe.travelsky.com|122.119.122.38|:80... connected.</span><br><span class="line">HTTP request sent, awaiting response... 200 OK</span><br><span class="line">Length: 239 [text/html]</span><br><span class="line">Saving to: “index.html.4”</span><br><span class="line"></span><br><span class="line">100%[=============================================================================================&gt;] 239         --.-K/s   <span class="keyword">in</span> 0s</span><br><span class="line"></span><br><span class="line">2015-07-01 17:57:20 (21.3 MB/s) - “index.html.4” saved [239/239]</span><br></pre></td></tr></table></figure><p>结果相当清晰，罪魁祸首就是ipv6。那为啥我换成我们自己的DNS就好了，我又跟负责网络的同学进行了沟通，原来他在DNS服务器上将IPV6的转发进行了关闭，所以可以很快返回。为了证明我的猜想是正确的，我又找了一台DNS为修改的服务器，让运维的同学将ipv6给禁用，经过测试果然奏效。至于如何关闭ipv6，网上有太多教程了。我在测试时候用了下面的方式，个人觉得太暴力，不推荐使用，不过应急可以试试。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> 1 &gt; /proc/sys/net/ipv6/conf/all/disable_ipv6</span><br></pre></td></tr></table></figure><hr><h3 id="Java-DNS-Lookup"><a href="#Java-DNS-Lookup" class="headerlink" title="Java  DNS Lookup"></a>Java  DNS Lookup</h3><p>上面通过DNS和系统层面都可以解决该问题，现在便到我们代码层面了。我们知道并非所有操作系统都支持 ipv6 协议，Java networking stack优先尝试检测ipv6 。当然你也可以利用系统属性禁用它。</p><h4 id="解析步骤如下："><a href="#解析步骤如下：" class="headerlink" title="解析步骤如下："></a>解析步骤如下：</h4><ol><li>Java networking stack 先确认底层操作系统是否支持ipv6。如果支持ipv6，Java将尝试使用ipv6 stack。</li><li>在双堆栈（dual-stack，指ipv4 stack+ipv6 stack）系统上，将创建一个ipv6 socket。在 separate-stack 系统上，事情要复杂得多，Java 将创建两个socket，一个给ipv4 一个给ipv6。</li><li>对于客户端TCP应用，一旦socket连上了，那 internet-protocol family type 就固定了，多余的那个socket就关闭了。对于服务器端TCP应用，由于不知道下一个客户端请求用什么ip family type，所以这两个 sockets 将继续保留。对于UDP应用，这两个sockets始终都需要保留。</li></ol><h4 id="java-ipv6-相关的系统参数"><a href="#java-ipv6-相关的系统参数" class="headerlink" title="java ipv6 相关的系统参数"></a>java ipv6 相关的系统参数</h4><ol><li>首选的协议栈：ipv4还是ipv6；</li><li>首选的地址族（address family type）：inet4 还是 inet6。</li></ol><p>由于在一个双堆栈系统上，ipv6 socket 能与 ipv4 和 ipv6 对端交互，所以 ipv6 stack 是默认首选项。你可以通过如下系统参数修改配置：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.net.preferIPv4Stack=&lt;true|false&gt;</span><br></pre></td></tr></table></figure><p>对应的 java 代码是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.lang.System.setProperty(<span class="string">"java.net.preferIPv4Stack"</span>, <span class="string">"true"</span>);</span><br></pre></td></tr></table></figure><p>默认我们首选 ipv4 地址族，也可以通过如下系统参数修改配置：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.net.preferIPv6Addresses=&lt;true|false&gt;</span><br></pre></td></tr></table></figure><p>对应的 java 代码是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.lang.System.setProperty(<span class="string">"java.net.preferIPv6Addresses"</span>, <span class="string">"false"</span>);</span><br></pre></td></tr></table></figure><p>然后我又用下面的代码进行了尝试，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.net.Inet4Address;</span><br><span class="line"><span class="keyword">import</span> java.net.InetAddress;</span><br><span class="line"><span class="keyword">import</span> java.net.UnknownHostException;</span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:15/6/18</span></span><br><span class="line"><span class="comment"> * Time:下午2:55</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * Description:IPV4 DNS解析速度测试</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String args[])</span> <span class="keyword">throws</span> UnknownHostException, InterruptedException </span>&#123;</span><br><span class="line">        java.lang.System.setProperty(<span class="string">"java.net.preferIPv4Stack"</span>, <span class="string">"true"</span>);</span><br><span class="line">        java.lang.System.setProperty(<span class="string">"java.net.preferIPv6Addresses"</span>, <span class="string">"false"</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            String host = <span class="string">"agibe.travelsky.com"</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">long</span> begin1 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            Inet4Address.getByName(host);</span><br><span class="line">            <span class="keyword">long</span> end1 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            System.out.println(end1 - begin1);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">            <span class="keyword">long</span> begin2 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            InetAddress.getAllByName(host);</span><br><span class="line">            <span class="keyword">long</span> end2 = <span class="keyword">new</span> Date().getTime();</span><br><span class="line">            System.out.println(end2 - begin2);</span><br><span class="line"></span><br><span class="line">System.out.println(<span class="string">"----"</span>);</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>*<span class="number">5</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><blockquote><p>看似简单的结论背后，如果去深挖，总会发现很多有趣的东西，enjoy yourself。</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;问题现象&quot;&gt;&lt;a href=&quot;#问题现象&quot; class=&quot;headerlink&quot; title=&quot;问题现象&quot;&gt;&lt;/a&gt;问题现象&lt;/h3&gt;&lt;p&gt;最近由于业务需求，重写了一套访问中航信IBE+的代理层。由于机票业务分为国内和国际两部分，这两部分都需要访问IBE+，之前是各自为战，各自都有一套访问逻辑。这样做的后果就是在流控(&lt;code&gt;IBE+对QPS有一定的限制&lt;/code&gt;)和接口管理方面得不到很好的控制，因此才写了这样的一个代理层。这样的设计其实没有任何问题，在微服务和SOA大行其道的今天这都是主流设计思路。该系统很快便上线了，但是很快我就发现IBE+的服务访问都非常慢，因为之前我没有涉及到这块，我就问了下之前负责这块的同事，他们说的确比较慢，我也就没有太上心。突然有天我打开生产环境的监控，我便发现了问题，大概30秒左右便有一次非常慢的操作。关于30秒的问题，之前我通过tcpdump抓包分析过，以为是Http Basic鉴权导致的，在新写的代理层中已经对此处进行了优化，但是结果看来不是很理想，然后便有了这个Case。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="HTTP" scheme="https://www.yangguo.info/tags/HTTP/"/>
    
  </entry>
  
  <entry>
    <title>生产环境难题解决方案</title>
    <link href="https://www.yangguo.info/2015/06/25/%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E8%B0%83%E8%AF%95%E9%9A%BE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>https://www.yangguo.info/2015/06/25/生产环境调试难题解决方案/</id>
    <published>2015-06-25T06:51:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="生产环境调试难题"><a href="#生产环境调试难题" class="headerlink" title="生产环境调试难题"></a>生产环境调试难题</h3><p>当我们的代码运行在生产环境时，各种千奇百怪的事情都有可能发生，而此时我们已经离开了舒服的开发环境，不能使用自己习惯的IDE来单步调试。当我们生产环境出现问题时，我们需要第一时间知道问题出在哪里。因此我们需要未雨绸缪做好准备，否者等问题发生时才通过漫无目的的查找日志，不但时间成本太高，并且不见得有效。</p><p>SOA，分布式，微服务….虽然带来了高可用和可扩展，但是同时也将系统架构的复杂度提升了好几个等级。经常我们会发现一个服务的不可用，是由于例外一个它依赖的服务导致的。持续交付和代码的不断更改，生产环境错误被找到的速度越来越快。然而如今，我们面对更大的困难是当错误发生时，我们如何提取错误的状态，比如变量值，错误，甚至逻辑代码块。</p><p>目前主流的解决方案有如下几种:</p><ol><li>分布式日志</li><li>高级的jstack</li><li>Btrack</li><li>定制的JVM代理</li></ol><a id="more"></a><hr><h3 id="分布式日志"><a href="#分布式日志" class="headerlink" title="分布式日志"></a>分布式日志</h3><p>分布式日志，就是我们每行日志都有清楚的上下文，我们只需要通过这行日志就能知道整个RPC调用链上发生的整个情况。对于分布式日志系统，业界成熟的产品有<strong>淘宝的鹰眼</strong>和<strong>点评的cat</strong>。原理就是RPC的第一跳，就生成一个全局唯一的UUID，该UUID会在整个RPC调用链中传递，并在需要打印日志的地方表现出来。很多日志库可以修改<strong>MDC(Mapped Diagnostic Context)</strong>来实现，如果没有也可以通过ThreadLocal来实现。至于该UUID如何在RPC调用链上传递，就需要自己在协议层面完善了。于此同时我们还可以结合<a href="https://www.elastic.co/products/logstash" target="_blank" rel="noopener">Logstash</a>或者<a href="https://www.loggly.com/" target="_blank" rel="noopener">Loggly</a>等日志管理工具，来简化日志管理。当然也有很多公司，通过<strong>rsyslog</strong>和<strong>syslog-ng</strong>甚至通过<strong>rsync</strong>来实现日志的集中化管理。</p><p>导致线程终结的未捕获异常点，通常包含许多有用信息。绝大多数的框架能够将这些异常信息打印出来，如果要自己实现，可以通过一个全局的<strong>Exception Handler</strong>来实现。在未捕获的异常发生时，我们在打印日志时，注意以下的3点。</p><ol><li>线程名字：根据你请求的参数来修改线程名，比如RPCID或者事务ID，这样我们查找日志时，便能一眼知道该线程在干嘛。</li><li>ThreadLocal(TLS)：在未捕获异常发生时，我们最好将ThreadLocal中的数据打印出来。因为除了数据和线程的名字，没有太多的程序能够到达异常点来记录TLS中的数据。这些现场数据保留的越多越好，当你追查问题时，便会从中受益。</li><li>MDC：它类似与TLS，MDC是Log4j或者Logback日志框架的一部分。它就是在指定的日志级别上创建了一个静态Map，相比TLS，它的功能更高级些。</li></ol><h3 id="先发制人的Jstack"><a href="#先发制人的Jstack" class="headerlink" title="先发制人的Jstack"></a>先发制人的Jstack</h3><p>jstack作为一款jdk自带的强大工具，被大多数的程序员所熟悉。它能够hook进正在运行的程序和输出被hook程序的堆栈，数据帧，Java虚拟机或者native，锁以及其它各种各样的元数据，它还能分析已经dump出来的堆栈信息。</p><p>虽然它很强大，但是我们都主要用在问题已出的事后，通过它来寻找蛛丝马迹定位问题所在。如果说能够在问题发生前或者问题发生中，我们就能够知道，并且将现场给保存下来，那么这无疑会降低问题的定位难度。下面给出一个当吞吐量达到一定阀值，自动jstack的例子。具体代码详见<a href="https://github.com/chengdedeng/jstack" target="_blank" rel="noopener">链接</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startScheduleTask</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   scheduler.scheduleAtFixedRate(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line"></span><br><span class="line">       <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">           checkThroughput();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">   &#125;, APP_WARMUP, POLLING_CYCLE, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkThroughput</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">int</span> throughput = adder.intValue(); <span class="comment">//the adder is inc’d when a message is processed</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (throughput &lt; MIN_THROUGHPUT) &#123;</span><br><span class="line"></span><br><span class="line">       Thread.currentThread().setName(<span class="string">"Throughput jstack thread: "</span> + throughput);</span><br><span class="line"></span><br><span class="line">       System.err.println(<span class="string">"Minimal throughput failed: executing jstack"</span>);</span><br><span class="line"></span><br><span class="line">       executeJstack();  <span class="comment">// See the code on GitHub to learn how this is done</span></span><br><span class="line"></span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   adder.reset();</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="有状态的Jstack"><a href="#有状态的Jstack" class="headerlink" title="有状态的Jstack"></a>有状态的Jstack</h3><p>Jstack存在的例外一个问题就是，虽然他提供了非常丰富的元数据，但是我们缺乏导致错误的实际状态。我们可以设置线程名称来解决。为了能够找到错误的根源，我们需要的不仅仅是一个堆栈追踪信息。我们以数据库查询为例，我们在程序中加入这样一行代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Thread.currentThread().setName(Context + TID + Params + current Time, ...);</span><br></pre></td></tr></table></figure><p>没有设置前，数据的元数据如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">“ pool-1-thread-1 ″ <span class="comment">#17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 in Object.wait() [0x000000013ebcc000]</span></span><br></pre></td></tr></table></figure><p>现在看起来是这样的：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">”Queue Processing Thread, MessageID: AB5CAD, <span class="built_in">type</span>: AnalyzeGraph, queue: ACTIVE_PROD, Transaction_ID: 5678956, Start Time: 10/8/2014 18:34″ <span class="comment">#17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 in Object.wait() [0x000000013ebcc000]</span></span><br></pre></td></tr></table></figure><p>现在的输出可以帮助我们更好的了解程序到底发生了什么。现在我们可以知道线程接收的参数，事务ID，消息ID。有了这些参数，我们就可以追溯问题，发现错误和解决问题。</p><h3 id="BTrace"><a href="#BTrace" class="headerlink" title="BTrace"></a>BTrace</h3><p>不论是日志还是jstack都不能对JVM的运行状态进行很好的可视化，BTrace使用Java的<strong>Attach</strong>技术，可以让我们无缝的将我们BTrace脚本挂到JVM上。通过脚本你可以获取到任何你想拿到的数据，在侵入性和安全性上都非常可靠，特别是定位线上问题的神器。BTrace的详细使用，可以见我的例外<a href="/2015/06/08/btrace笔记">一篇文章</a>。</p><p>让我们简单的看一段<strong>BTrace</strong>脚本：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Profiling</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Property</span></span><br><span class="line">    Profiler profiler = BTraceUtils.Profiling.newProfiler();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"/info\\.yangguo\\..*/"</span>, method = <span class="string">"/.*/"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">entry</span><span class="params">(@ProbeMethodName(fqn = <span class="keyword">true</span>)</span> String probeMethod) </span>&#123;</span><br><span class="line">        BTraceUtils.Profiling.recordEntry(profiler, probeMethod);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"/info\\.yangguo\\..*/"</span>, method = <span class="string">"/.*/"</span>, location = <span class="meta">@Location</span>(value = Kind.RETURN))</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exit</span><span class="params">(@ProbeMethodName(fqn = <span class="keyword">true</span>)</span> String probeMethod, @Duration <span class="keyword">long</span> duration) </span>&#123;</span><br><span class="line">        BTraceUtils.Profiling.recordExit(profiler, probeMethod, duration);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnTimer</span>(<span class="number">5000</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">timer</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        BTraceUtils.Profiling.printSnapshot(<span class="string">"Performance profile"</span>, profiler);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>BTrace是定位具体问题的有效工具，但是它的开销是非常显著的，使用的时候要小心，避免过高的开销影响重要业务。</p><h3 id="自定义代理"><a href="#自定义代理" class="headerlink" title="自定义代理"></a>自定义代理</h3><p>跟BTrace一样，我们也可以自己定义Java代理。在Java SE5中，Instrument就已经被加入，Java SE6中，更是实现了动态Instrument。Instrument功能从本地代码中解放出来，使之可以使用使用Java代码在不改变现有代码的前提下，在不重启应用的前提下实现对JVM的监测甚至替换和修改某些类的定义。它有点类似于虚拟机级别的AOP，使开发者不用对jdk做任何改动，就可以实现AOP功能。BTrace的实现就是依靠该功能实现的。</p><p>“java.lang.instrument”包的具体实现，依赖于 JVMTI。JVMTI（Java Virtual Machine Tool Interface）是一套由 Java 虚拟机提供的，为 JVM 相关的工具提供的本地编程接口集合。JVMTI 是从 Java SE 5 开始引入，整合和取代了以前使用的 Java Virtual Machine Profiler Interface (JVMPI) 和 the Java Virtual Machine Debug Interface (JVMDI)，而在 Java SE 6 中，JVMPI 和 JVMDI 已经消失了。JVMTI 提供了一套”代理”程序机制，可以支持第三方工具程序以代理的方式连接和访问 JVM，并利用 JVMTI 提供的丰富的编程接口，完成很多跟 JVM 相关的功能。</p><p>另外，对 native 的 Instrumentation 也是 Java SE 6 的一个崭新的功能，这使以前无法完成的功能 —— 对 native 接口的 instrumentation 可以在 Java SE 6 中，通过一个或者一系列的 prefix 添加而得以完成。</p><p>测试代码可以具体参考<a href="https://github.com/chengdedeng/demo" target="_blank" rel="noopener">该项目</a>中的<strong>attch_api</strong>包下面的代码。</p><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>很多时候个人价值的体现就是当有问题发生时，看谁能够更快速的解决问题。今天对于一个需要提供7*24小时服务的应用来说，别说停机，就是部分服务出现问题都是不可容忍的。谁能在最短的时间修复问题，并且快速部署，谁就更能创造价值。</p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;生产环境调试难题&quot;&gt;&lt;a href=&quot;#生产环境调试难题&quot; class=&quot;headerlink&quot; title=&quot;生产环境调试难题&quot;&gt;&lt;/a&gt;生产环境调试难题&lt;/h3&gt;&lt;p&gt;当我们的代码运行在生产环境时，各种千奇百怪的事情都有可能发生，而此时我们已经离开了舒服的开发环境，不能使用自己习惯的IDE来单步调试。当我们生产环境出现问题时，我们需要第一时间知道问题出在哪里。因此我们需要未雨绸缪做好准备，否者等问题发生时才通过漫无目的的查找日志，不但时间成本太高，并且不见得有效。&lt;/p&gt;
&lt;p&gt;SOA，分布式，微服务….虽然带来了高可用和可扩展，但是同时也将系统架构的复杂度提升了好几个等级。经常我们会发现一个服务的不可用，是由于例外一个它依赖的服务导致的。持续交付和代码的不断更改，生产环境错误被找到的速度越来越快。然而如今，我们面对更大的困难是当错误发生时，我们如何提取错误的状态，比如变量值，错误，甚至逻辑代码块。&lt;/p&gt;
&lt;p&gt;目前主流的解决方案有如下几种:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分布式日志&lt;/li&gt;
&lt;li&gt;高级的jstack&lt;/li&gt;
&lt;li&gt;Btrack&lt;/li&gt;
&lt;li&gt;定制的JVM代理&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Thread" scheme="https://www.yangguo.info/tags/Thread/"/>
    
      <category term="BTrace" scheme="https://www.yangguo.info/tags/BTrace/"/>
    
  </entry>
  
  <entry>
    <title>BTrace笔记</title>
    <link href="https://www.yangguo.info/2015/06/08/btrace%E7%AC%94%E8%AE%B0/"/>
    <id>https://www.yangguo.info/2015/06/08/btrace笔记/</id>
    <published>2015-06-08T11:31:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>BTrace作为线上问题定位神器，它在侵入、安全、资源占用等方面表现的都非常出色。本文记录了作者平时工作中使用Btrace的场景，以供大家参考。</p></blockquote><h3 id="BTrace限制"><a href="#BTrace限制" class="headerlink" title="BTrace限制"></a>BTrace限制</h3><p>关于BTrace的安装配置使用，网络上有太多的教程了，本文就不再重复造轮子了，但是还是非常有必要再次重申BTrace的限制。因为不正当的使用Btrace可能根本拿不到自己想要的结果，甚至导致JVM崩溃，建议脚本使用之前先经过验证，然后再使用到线上环境。</p><p>使用Btrace时，需要确保追踪的行动是只读的（即：追踪行动不能修改程序的状态）和有限的（即：追踪行动需要在有限的时间内终止），一个追踪行为需要满足如下的限制：</p><ul><li>不能创建新的对象</li><li>不能创建新的数组</li><li>不能抛出异常</li><li>不能捕获异常</li><li>不能对实例和静态方法随意调用，只有<strong>com.sun.btrace.BTraceUtils</strong>中的public static方法或者当前脚本中申明的方法，可以被BTrace程序调用。</li><li>(1.2之前)不能存在实例字段和方法。BTrace类只允许静态公共void返回方法，并且所有字段必须是静态的。</li><li>不能将目标程序中的类和对象中的静态或者实例字段指派给BTrace程序。但是，可以将BTrace类自己的静态字段（“trace state”是可以改变的）指派给自己。因为引用的传递可能导致目标程序别修改，而BTrace自身的跟踪字段只有BTrace自己在使用，所以不怕修改。</li><li>不能有外部，内部，嵌套或者本地的类。</li><li>不能有<strong>synchronized</strong>块和方法。</li><li>不能有循环（for，while，do…..while）。</li><li>不能继承抽象类（父类只能为java.lang.Object）。</li><li>不能实现接口。</li><li>不能有断言语句。</li><li>不能使用<strong>class</strong>保留字。</li></ul><a id="more"></a><p>以上的限制可以通过通过<strong>unsafe</strong>模式绕过。追踪脚本和引擎都必须设置为<strong>unsafe</strong>模式。脚本需要使用注解为<strong>@BTrace(unsafe = true)</strong>，需要修改BTrace安装目录下bin中btrace脚本将<strong>-Dcom.sun.btrace.unsafe=false</strong>改为<strong>-Dcom.sun.btrace.unsafe=true</strong>。</p><p>注：关于<strong>unsafe</strong>的使用，如果你的程序一旦被btrace追踪过，那么unsafe的设置会一直伴随该进程的整个生命周期。如果你修改了<strong>unsafe</strong>的设置，只有通过重启目标进程，才能获得想要的结果。所以该用法不是很好使用，如果你的应用不能随便重启，那么你在第一次使用btrace最终目标进程之前，先想好到底使用那种模式来启动引擎。</p><hr><h3 id="性能分析"><a href="#性能分析" class="headerlink" title="性能分析"></a>性能分析</h3><p>大家经常会发现自己某一个服务变慢，但是由于这个服务背后由很多的业务逻辑或者方法块构成，这个时候就不好定位到底慢在哪个地方。<strong>Profiling</strong>就可以很好的解决该问题，我们只需要大概的定位问题可能存在的地方，通过包路径的模糊匹配，就可以很快找到问题所在。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Profiling</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Property</span></span><br><span class="line">    Profiler profiler = BTraceUtils.Profiling.newProfiler();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"/info\\.yangguo\\..*/"</span>, method = <span class="string">"/.*/"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">entry</span><span class="params">(@ProbeMethodName(fqn = <span class="keyword">true</span>)</span> String probeMethod) </span>&#123;</span><br><span class="line">        BTraceUtils.Profiling.recordEntry(profiler, probeMethod);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"/info\\.yangguo\\..*/"</span>, method = <span class="string">"/.*/"</span>, location = <span class="meta">@Location</span>(value = Kind.RETURN))</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">exit</span><span class="params">(@ProbeMethodName(fqn = <span class="keyword">true</span>)</span> String probeMethod, @Duration <span class="keyword">long</span> duration) </span>&#123;</span><br><span class="line">        BTraceUtils.Profiling.recordExit(profiler, probeMethod, duration);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnTimer</span>(<span class="number">5000</span>)</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">timer</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        BTraceUtils.Profiling.printSnapshot(<span class="string">"Performance profile"</span>, profiler);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然如果你怀疑一个类的某一个方法，也可以采用精准定位的方式来进行追踪。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HttpClientSystemDefaultDnsResolverTracer</span> </span>&#123;</span><br><span class="line">    <span class="meta">@TLS</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">long</span> beginTime;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"org.apache.http.impl.conn.SystemDefaultDnsResolver"</span>,</span><br><span class="line">            method = <span class="string">"resolve"</span></span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">traceGetByNameBegin</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        beginTime = timeMillis();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"org.apache.http.impl.conn.SystemDefaultDnsResolver"</span>,</span><br><span class="line">            method = <span class="string">"resolve"</span>,</span><br><span class="line">            location = <span class="meta">@Location</span>(Kind.RETURN)</span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">traceGetByNameEnd</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        println(strcat(strcat(<span class="string">"org.apache.http.impl.conn.SystemDefaultDnsResolver.resolve 耗时:"</span>, str(timeMillis() - beginTime)), <span class="string">"ms"</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你只想定位执行时间超过某个阀值的函数，也可以使用<strong>@Duration</strong>来解决。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DurationTracer</span> </span>&#123;</span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"/info\\.yangguo\\.demo\\..*/"</span>,</span><br><span class="line">            method = <span class="string">"/.*/"</span>,</span><br><span class="line">            location = <span class="meta">@Location</span>(Kind.RETURN)</span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">trace</span><span class="params">(@ProbeClassName String pcn,</span></span></span><br><span class="line"><span class="function"><span class="params">                             @ProbeMethodName String pmn,</span></span></span><br><span class="line"><span class="function"><span class="params">                             @Duration <span class="keyword">long</span> duration)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//duration的单位是纳秒</span></span><br><span class="line">        <span class="keyword">if</span> (duration &gt; <span class="number">1000</span> * <span class="number">1000</span> * <span class="number">3</span>) &#123;</span><br><span class="line">            print(Strings.strcat(Strings.strcat(pcn, <span class="string">"."</span>), pmn));</span><br><span class="line">            print(<span class="string">" 耗时:"</span>);</span><br><span class="line">            print(duration);</span><br><span class="line">            println(<span class="string">"纳秒,堆栈信息如下"</span>);</span><br><span class="line">            jstack();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="异常分析"><a href="#异常分析" class="headerlink" title="异常分析"></a>异常分析</h3><p>有时候，开发人员对异常的处理不合理，导致某些重要异常别人为吃掉，并且没有任何日志或者日志不详细，导致问题定位困难，那么我们可以使用下面的Tracer来处理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OnThrow</span> </span>&#123;</span><br><span class="line">    <span class="comment">// store current exception in a thread local</span></span><br><span class="line"><span class="comment">// variable (@TLS annotation). Note that we can't</span></span><br><span class="line"><span class="comment">// store it in a global variable!</span></span><br><span class="line">    <span class="meta">@TLS</span></span><br><span class="line">    <span class="keyword">static</span> Throwable currentException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// introduce probe into every constructor of java.lang.Throwable</span></span><br><span class="line"><span class="comment">// class and store "this" in the thread local variable.</span></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"java.lang.Throwable"</span>,</span><br><span class="line">            method = <span class="string">"&lt;init&gt;"</span></span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onthrow</span><span class="params">(@Self Throwable self)</span> </span>&#123;</span><br><span class="line">        currentException = self;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"java.lang.Throwable"</span>,</span><br><span class="line">            method = <span class="string">"&lt;init&gt;"</span></span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onthrow1</span><span class="params">(@Self Throwable self, String s)</span> </span>&#123;</span><br><span class="line">        currentException = self;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"java.lang.Throwable"</span>,</span><br><span class="line">            method = <span class="string">"&lt;init&gt;"</span></span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onthrow1</span><span class="params">(@Self Throwable self, String s, Throwable cause)</span> </span>&#123;</span><br><span class="line">        currentException = self;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"java.lang.Throwable"</span>,</span><br><span class="line">            method = <span class="string">"&lt;init&gt;"</span></span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onthrow2</span><span class="params">(@Self Throwable self, Throwable cause)</span> </span>&#123;</span><br><span class="line">        currentException = self;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// when any constructor of java.lang.Throwable returns</span></span><br><span class="line"><span class="comment">// print the currentException's stack trace.</span></span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"java.lang.Throwable"</span>,</span><br><span class="line">            method = <span class="string">"&lt;init&gt;"</span>,</span><br><span class="line">            location = <span class="meta">@Location</span>(Kind.RETURN)</span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onthrowreturn</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (currentException != <span class="keyword">null</span>) &#123;</span><br><span class="line">            Threads.jstack(currentException);</span><br><span class="line">            println(<span class="string">"====================="</span>);</span><br><span class="line">            currentException = <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="参数及返回结果"><a href="#参数及返回结果" class="headerlink" title="参数及返回结果"></a>参数及返回结果</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MethodArgsAndReturnTracer</span> </span>&#123;</span><br><span class="line">    <span class="meta">@OnMethod</span>(</span><br><span class="line">            clazz = <span class="string">"info.yangguo.demo.btrace.Adder"</span>,</span><br><span class="line">            method = <span class="string">"execute"</span>,</span><br><span class="line">            location = <span class="meta">@Location</span>(Kind.RETURN)</span><br><span class="line">    )</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">traceExecute</span><span class="params">(@Self Adder instance, <span class="keyword">int</span> arg1,<span class="keyword">int</span> arg2, @Return <span class="keyword">int</span> result)</span> </span>&#123;</span><br><span class="line">        println(<span class="string">"Adder.execute"</span>);</span><br><span class="line">        println(strcat(<span class="string">"arg1 is:"</span>, str(arg1)));</span><br><span class="line">        println(strcat(<span class="string">"arg2 is:"</span>, str(arg2)));</span><br><span class="line">        println(strcat(<span class="string">"maxResult is:"</span>, str(get(field(<span class="string">"info.yangguo.demo.btrace.Adder"</span>, <span class="string">"maxResult"</span>), instance))));</span><br><span class="line">        println(strcat(<span class="string">"return value is:"</span>, str(result)));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="jdbc问题追踪"><a href="#jdbc问题追踪" class="headerlink" title="jdbc问题追踪"></a>jdbc问题追踪</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JdbcQueries</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Map&lt;Statement, String&gt; preparedStatementDescriptions = Collections.newWeakMap();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation histogram = Aggregations.newAggregation(AggregationFunction.QUANTIZE);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation average = Aggregations.newAggregation(AggregationFunction.AVERAGE);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation max = Aggregations.newAggregation(AggregationFunction.MAXIMUM);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation min = Aggregations.newAggregation(AggregationFunction.MINIMUM);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation sum = Aggregations.newAggregation(AggregationFunction.SUM);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation count = Aggregations.newAggregation(AggregationFunction.COUNT);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Aggregation globalCount = Aggregations.newAggregation(AggregationFunction.COUNT);</span><br><span class="line">    <span class="meta">@TLS</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String preparingStatement;</span><br><span class="line">    <span class="meta">@TLS</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> String executingStatement;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * If "--stack" is passed on command line, print the Java stack trace of the JDBC statement.</span></span><br><span class="line"><span class="comment">     * &lt;p/&gt;</span></span><br><span class="line"><span class="comment">     * Otherwise we print the SQL.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> useStackTrace = Sys.$(<span class="number">2</span>) != <span class="keyword">null</span> &amp;&amp; Strings.strcmp(<span class="string">"--stack"</span>, Sys.$(<span class="number">2</span>)) == <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// The first couple of probes capture whenever prepared statement and callable statements are</span></span><br><span class="line">    <span class="comment">// instantiated, in order to let us track what SQL they contain.</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Capture SQL used to create prepared statements.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> args the list of method parameters. args[1] is the SQL.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"+java.sql.Connection"</span>, method = <span class="string">"/prepare.*/"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onPrepare</span><span class="params">(AnyType[] args)</span> </span>&#123;</span><br><span class="line">        preparingStatement = useStackTrace ? Threads.jstackStr() : str(args[<span class="number">0</span>]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Cache SQL associated with a prepared statement.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> preparedStatement the return value from the prepareXxx() method.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"+java.sql.Connection"</span>, method = <span class="string">"/prepare.*/"</span>, location = <span class="meta">@Location</span>(Kind.RETURN))</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onPrepareReturn</span><span class="params">(@Return Statement preparedStatement)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (preparingStatement != <span class="keyword">null</span>) &#123;</span><br><span class="line">            print(<span class="string">"P"</span>); <span class="comment">// Debug Prepared</span></span><br><span class="line">            Collections.put(preparedStatementDescriptions, preparedStatement, preparingStatement);</span><br><span class="line">            preparingStatement = <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The next couple of probes intercept the execution of a statement. If it execute with no-args,</span></span><br><span class="line">    <span class="comment">// then it must be a prepared statement or callable statement. Get the SQL from the probes up above.</span></span><br><span class="line">    <span class="comment">// Otherwise the SQL is in the first argument.</span></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"+java.sql.Statement"</span>, method = <span class="string">"/execute.*/"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onExecute</span><span class="params">(@Self Object currentStatement, AnyType[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (args.length == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// No SQL argument; lookup the SQL from the prepared statement</span></span><br><span class="line">            executingStatement = Collections.get(preparedStatementDescriptions, currentStatement);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// Direct SQL in the first argument</span></span><br><span class="line">            executingStatement = useStackTrace ? Threads.jstackStr() : str(args[<span class="number">0</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnMethod</span>(clazz = <span class="string">"+java.sql.Statement"</span>, method = <span class="string">"/execute.*/"</span>, location = <span class="meta">@Location</span>(Kind.RETURN))</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onExecuteReturn</span><span class="params">(@Duration <span class="keyword">long</span> durationL)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (executingStatement == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        print(<span class="string">"X"</span>); <span class="comment">// Debug Executed</span></span><br><span class="line"></span><br><span class="line">        AggregationKey key = Aggregations.newAggregationKey(executingStatement);</span><br><span class="line">        <span class="keyword">int</span> duration = (<span class="keyword">int</span>) durationL / <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line">        Aggregations.addToAggregation(histogram, key, duration);</span><br><span class="line">        Aggregations.addToAggregation(average, key, duration);</span><br><span class="line">        Aggregations.addToAggregation(max, key, duration);</span><br><span class="line">        Aggregations.addToAggregation(min, key, duration);</span><br><span class="line">        Aggregations.addToAggregation(sum, key, duration);</span><br><span class="line">        Aggregations.addToAggregation(count, key, duration);</span><br><span class="line">        Aggregations.addToAggregation(globalCount, duration);</span><br><span class="line"></span><br><span class="line">        executingStatement = <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@OnEvent</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">onEvent</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Top 10 queries only</span></span><br><span class="line">        Aggregations.truncateAggregation(histogram, <span class="number">10</span>);</span><br><span class="line">        println(<span class="string">"---------------------------------------------"</span>);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Count"</span>, count);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Min"</span>, min);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Max"</span>, max);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Average"</span>, average);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Sum"</span>, sum);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Histogram"</span>, histogram);</span><br><span class="line">        Aggregations.printAggregation(<span class="string">"Global Count"</span>, globalCount);</span><br><span class="line">        println(<span class="string">"---------------------------------------------"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="死锁排查"><a href="#死锁排查" class="headerlink" title="死锁排查"></a>死锁排查</h3><p>很多时候，我们怀疑程序是否有死锁，可以用下面的脚本扫描下，非常简单方便。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@BTrace</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Deadlock</span> </span>&#123;</span><br><span class="line">    <span class="meta">@OnTimer</span>(<span class="number">4000</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">print</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        deadlocks();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我只是将我工作中经常用到的一些列举出来了，还有很多没有列举，可以详见<a href="https://kenai.com/projects/btrace/pages/UserGuide" target="_blank" rel="noopener">链接</a>。</p><p>参考文章：<br><a href="http://agapple.iteye.com/blog/1005918" target="_blank" rel="noopener">Btrace一些你不知道的事</a></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;BTrace作为线上问题定位神器，它在侵入、安全、资源占用等方面表现的都非常出色。本文记录了作者平时工作中使用Btrace的场景，以供大家参考。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;BTrace限制&quot;&gt;&lt;a href=&quot;#BTrace限制&quot; class=&quot;headerlink&quot; title=&quot;BTrace限制&quot;&gt;&lt;/a&gt;BTrace限制&lt;/h3&gt;&lt;p&gt;关于BTrace的安装配置使用，网络上有太多的教程了，本文就不再重复造轮子了，但是还是非常有必要再次重申BTrace的限制。因为不正当的使用Btrace可能根本拿不到自己想要的结果，甚至导致JVM崩溃，建议脚本使用之前先经过验证，然后再使用到线上环境。&lt;/p&gt;
&lt;p&gt;使用Btrace时，需要确保追踪的行动是只读的（即：追踪行动不能修改程序的状态）和有限的（即：追踪行动需要在有限的时间内终止），一个追踪行为需要满足如下的限制：&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;li&gt;不能对实例和静态方法随意调用，只有&lt;strong&gt;com.sun.btrace.BTraceUtils&lt;/strong&gt;中的public static方法或者当前脚本中申明的方法，可以被BTrace程序调用。&lt;/li&gt;
&lt;li&gt;(1.2之前)不能存在实例字段和方法。BTrace类只允许静态公共void返回方法，并且所有字段必须是静态的。&lt;/li&gt;
&lt;li&gt;不能将目标程序中的类和对象中的静态或者实例字段指派给BTrace程序。但是，可以将BTrace类自己的静态字段（“trace state”是可以改变的）指派给自己。因为引用的传递可能导致目标程序别修改，而BTrace自身的跟踪字段只有BTrace自己在使用，所以不怕修改。&lt;/li&gt;
&lt;li&gt;不能有外部，内部，嵌套或者本地的类。&lt;/li&gt;
&lt;li&gt;不能有&lt;strong&gt;synchronized&lt;/strong&gt;块和方法。&lt;/li&gt;
&lt;li&gt;不能有循环（for，while，do…..while）。&lt;/li&gt;
&lt;li&gt;不能继承抽象类（父类只能为java.lang.Object）。&lt;/li&gt;
&lt;li&gt;不能实现接口。&lt;/li&gt;
&lt;li&gt;不能有断言语句。&lt;/li&gt;
&lt;li&gt;不能使用&lt;strong&gt;class&lt;/strong&gt;保留字。&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Thread" scheme="https://www.yangguo.info/tags/Thread/"/>
    
  </entry>
  
  <entry>
    <title>线程转储分析</title>
    <link href="https://www.yangguo.info/2015/04/01/%E7%BA%BF%E7%A8%8B%E8%BD%AC%E5%82%A8/"/>
    <id>https://www.yangguo.info/2015/04/01/线程转储/</id>
    <published>2015-04-01T09:31:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近好几个同事都向我问起过java<strong>线程转储</strong>方面的知识，都是些常见的问题，如：系统负载过高，系统吞吐率降低，系统挂起等等。这些问题的定位和解决都离不开<strong>线程转储</strong>，因为这些问题在测试环境复现的难度较大，只有在生产环境进行<strong>ThreadDump</strong>，才能快速定位问题。</p></blockquote><h3 id="线程状态"><a href="#线程状态" class="headerlink" title="线程状态"></a>线程状态</h3><p>在具体分析线程转储数据之前，我们首先要明确线程的状态。<strong>java.lang.Thread.State</strong>枚举类中定义了如下几种类型：</p><ol><li><strong>NEW</strong>：线程创建尚未启动。</li><li><strong>RUNNABLE</strong>：包括操作系统线程状态中的Ready和Running，可能在等待时间片或者正在执行。</li><li><strong>BLOCKED</strong>：线程被阻塞。</li><li><strong>WAITING</strong>：不会分配CPU执行时间，直到别的线程显式的唤醒，否则无限期等待。LockSupport.park()，没有设置Timeout参数的Object.wait()和Thread.join()，会导致此现象。</li><li><strong>TIMED_WAITING</strong>：不会分配CPU执行时间，直到系统自动唤醒，不需要别的线程显示唤醒。Thread.sleep()，LockSupport.parkNanos()，LockSupport.parkUntil()，设置了超时时间的Object.wait()和Thread.join()，会让线程进入有限期等待。</li><li><strong>TERMINATED</strong>：线程执行结束。</li></ol><a id="more"></a><blockquote><p>很多人分不清楚<strong>阻塞</strong>和<strong>等待</strong>的区别，导致在分析线程转储时出现偏差。阻塞状态与等待状态的区别是：阻塞状态的线程是在等待一个排它锁，直到别的线程释放该排它锁，该线程获取到该锁才能退出阻塞状态；而等待状态的线程则是等待一段时间，由系统唤醒或者别的线程唤醒，该线程便退出等待状态。</p></blockquote><p>线程状态转化见下图：<br><img src="/images/thread_state.png" alt="thread_state"></p><p>在任意一个时刻，线程只能处于其中的一种状态。</p><p>上面的状态颗粒度比较大，stackoverflow上有个哥们将状态定义为如下11种。在我们强制dump出来的转储中，更多的是表现为如下状态中的一种。至于这11种状态的是否权威，不敢妄下定论。原文<a href="http://stackoverflow.com/questions/1827543/java-threads-interpreting-thread-states-of-a-running-jvm" target="_blank" rel="noopener">见此</a></p><ol><li>NEW: Just starting up, i.e., in process of being initialized.</li><li>NEW_TRANS: Corresponding transition state (not used, included for completness).</li><li>IN_NATIVE: Running in native code.</li><li>IN_NATIVE_TRANS: Corresponding transition state.</li><li>IN_VM: Running in VM.</li><li>IN_VM_TRANS: Corresponding transition state.</li><li>IN_JAVA: Running in Java or in stub code.</li><li>IN_JAVA_TRANS: Corresponding transition state (not used, included for completness).</li><li>BLOCKED: Blocked in vm.</li><li>BLOCKED_TRANS: Corresponding transition state.</li><li>UNINITIALIZED</li></ol><p>java的线程转储指的是JVM中在某一个给定的时刻运行的所有线程的快照。一个线程转储可能包含一个单独的线程或者多个线程。在多线程环境中，如Java EE应用服务器，将会有许多线程和线程组。转储中的每一个线程都有一自己独立的逻辑，这些逻辑信息将会在堆栈信息中体现。</p><p>线程转储可以通过如下的方式生成：</p><ol><li>Unix：kill -3 <pid>，输出到了/proc/<pid>/fd/1</pid></pid></li><li>Windows：CTRL+BREAK</li><li>jstack：jstack <pid> &gt;&gt; 输出文件</pid></li></ol><p>在Linux上，大家可能都碰见过这样的问题：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Unable to open socket file: target process not responding or HotSpot VM not loaded</span><br><span class="line">The -F option can be used when the target process is not responding</span><br></pre></td></tr></table></figure><p>就是用jstack <pid>是拿不到thread dump的，必须加<strong>-F</strong>来强制dump。上面的问题是其实很简单，就是pid路径问题导致的，Google一下就清楚了。我想说的是，加<strong>-F</strong> dump出来的内容是不能用<a href="https://java.net/projects/tda" target="_blank" rel="noopener">TDA</a>分析的。如果不是真正的进程挂起，如果又想使用<strong>TDA</strong>进行分析的话，可以使用第一种方式进行dump。但有时候，的确必须要使用<strong>-F</strong>才能获得dump信息，比如程序挂起了。由于jstack -F/m就会使用到<strong>SA</strong>(The Serviceability Agent)，我们平常不加-F/-m的时候默认走的是<strong>Instrumentation</strong>的<strong>attach</strong>操作，知道这点非常重要。</pid></p><h3 id="转储说明"><a href="#转储说明" class="headerlink" title="转储说明"></a>转储说明</h3><p>线程转储生成好之后，我们便可以开始着手分析转储信息了，下面会给出几个例子逐一分析。</p><p><strong>例子1</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"http-bio-8080-exec-1-SendThread(192.168.161.36:2181)"</span> daemon prio=10 tid=0x00007f797425b800 nid=0x3884 runnable [0x00007f79795ac000]</span><br><span class="line">   java.lang.Thread.State: RUNNABLE</span><br><span class="line">at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)</span><br><span class="line">at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:228)</span><br><span class="line">at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:81)</span><br><span class="line">at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:87)</span><br><span class="line">- locked &lt;0x00000000f2bf18e0&gt; (a sun.nio.ch.Util<span class="variable">$2</span>)</span><br><span class="line">- locked &lt;0x00000000f2bf18f0&gt; (a java.util.Collections<span class="variable">$UnmodifiableSet</span>)</span><br><span class="line">- locked &lt;0x00000000f2bf1898&gt; (a sun.nio.ch.EPollSelectorImpl)</span><br><span class="line">at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:98)</span><br><span class="line">at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:349)</span><br><span class="line">at org.apache.zookeeper.ClientCnxn<span class="variable">$SendThread</span>.run(ClientCnxn.java:1081)</span><br></pre></td></tr></table></figure><ul><li>http-bio-8080-exec-1-SendThread(192.168.161.36:2181)：线程的名字</li><li>daemon：守护线程</li><li>prio=10：线程的优先级（默认是5）</li><li>tid：Java的线程Id（线程在当前虚拟机中的唯一标识）</li><li>nid：线程本地标识</li><li>runnable：线程的状态</li><li>[0x00007f79795ac000]：当前运行的线程在堆中的地址范围</li></ul><p>这个线程转储的剩余部分是调用堆栈，这个线程（http-bio-8080-exec-1-SendThread(192.168.161.36:2181)）是操作系统守护线程，当前正在执行一个本地方法epollWait。该native线程正处于RUNNABLE的状态，</p><p><strong>例子2</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"http-bio-8080-exec-1"</span> daemon prio=10 tid=0x0000000002172800 nid=0x3882 waiting on condition [0x00007f798c292000]</span><br><span class="line">   java.lang.Thread.State: WAITING (parking)</span><br><span class="line">at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">- parking to <span class="built_in">wait</span> <span class="keyword">for</span>  &lt;0x00000000f2a44710&gt; (a java.util.concurrent.locks.AbstractQueuedSynchronizer<span class="variable">$ConditionObject</span>)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)</span><br><span class="line">at java.util.concurrent.locks.AbstractQueuedSynchronizer<span class="variable">$ConditionObject</span>.await(AbstractQueuedSynchronizer.java:2043)</span><br><span class="line">at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)</span><br><span class="line">at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor<span class="variable">$Worker</span>.run(ThreadPoolExecutor.java:615)</span><br><span class="line">at java.lang.Thread.run(Thread.java:722)</span><br></pre></td></tr></table></figure><p>该转储表示守护线程<strong>http-bio-8080-exec-1</strong>会一直等待，直到ThreadPoolExecutor上有需要执行的任务分配给该线程执行(LockSupport.unpark)，它才能从LockSupport.park中解脱出来。</p><p><strong>例子3</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"RMI Scheduler(0)"</span> daemon prio=9 tid=7f94661c1800 nid=0x11b479000 waiting on condition [11b478000]</span><br><span class="line">   java.lang.Thread.State: TIMED_WAITING (parking)</span><br><span class="line">at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">- parking to <span class="built_in">wait</span> <span class="keyword">for</span>  &lt;7f40deae0&gt; (a java.util.concurrent.locks.AbstractQueuedSynchronizer<span class="variable">$ConditionObject</span>)</span><br><span class="line">at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196)</span><br><span class="line">at java.util.concurrent.locks.AbstractQueuedSynchronizer<span class="variable">$ConditionObject</span>.awaitNanos(AbstractQueuedSynchronizer.java:2025)</span><br><span class="line">at java.util.concurrent.DelayQueue.take(DelayQueue.java:164)</span><br><span class="line">at java.util.concurrent.ScheduledThreadPoolExecutor<span class="variable">$DelayedWorkQueue</span>.take(ScheduledThreadPoolExecutor.java:609)</span><br><span class="line">at java.util.concurrent.ScheduledThreadPoolExecutor<span class="variable">$DelayedWorkQueue</span>.take(ScheduledThreadPoolExecutor.java:602)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:957)</span><br><span class="line">at java.util.concurrent.ThreadPoolExecutor<span class="variable">$Worker</span>.run(ThreadPoolExecutor.java:917)</span><br><span class="line">at java.lang.Thread.run(Thread.java:695)</span><br><span class="line"></span><br><span class="line">   Locked ownable synchronizers:</span><br><span class="line">- None</span><br></pre></td></tr></table></figure><p>线程<strong>RMI Scheduler</strong>不会分配CPU执行时间，直到系统自动唤醒，不需要别的线程显示唤醒。它由于调用了LockSupport.parkNanos()进入了有限期等待。</p><hr><h3 id="案例分析"><a href="#案例分析" class="headerlink" title="案例分析"></a>案例分析</h3><h4 id="CPU负载高"><a href="#CPU负载高" class="headerlink" title="CPU负载高"></a>CPU负载高</h4><p>Java进程CPU占用率居高不下，导致系统吞吐率下降，这种问题对很多初级码农来说简直就是梦魇。如果想快速的定位CPU高的原因，线程转储绝对是第一选择。CPU过高一般原因分为CPU密集型和死循环，这两种情况我们分开来分析。</p><p>死循环分析非常简单，步骤如下：</p><ol><li>在CPU占用率高的时候，通过<strong>top -H</strong>或者<strong>ps H -eo user,pid,ppid,tid,time,%cpu,cmd –sort=%cpu</strong>将CPU占用率高的线程的tid给找出来，并即时线程转储。</li><li>将上面的找到的tid进行转化为16进制，然后在线程转储中查找对应的线程号，都会找到对应的nid标示。</li><li>定位之后，通过转储中的精确的行号，可以定位到代码中相应的位置，便可以迅速确定原因。</li></ol><p>死循环一般是逻辑错误导致的，我们在代码中应该慎用自旋。在一些带有状态机逻辑的代码中，加入次数限制，防止程序跑飞。</p><p>CPU密集型分析跟死循环不一样，因为你通过查看线程的CPU使用率，你会发现每个线程都不是很高，但是多个加起来之后就不容乐观。这类问题，步骤如下：</p><ol><li>在CPU占用率高的时候，通过<strong>top -H</strong>或者<strong>ps H -eo user,pid,ppid,tid,time,%cpu,cmd –sort=%cpu</strong>将CPU占用率高的前10个线程的tid给找出来，并即时线程转储。</li><li>将上面的找到的tid进行转化为16进制，然后在线程转储中查找对应的线程号，将能够找到的部分标记出来。</li><li>然后在标记出来的线程转储中寻找共性。如果通过所有的线程转储，发现线程正在执行同一个方法（同样的行号），几乎就可以确定这就是罪魁祸首了。那就可以查看代码，来做代码级别的分析了，问题便迎刃而解了。</li></ol><p>CPU密集型，一般原因是算法效率低导致的。例如，正则表达式写的差，该用Map或者Set结构的却用了List来遍历。</p><p>线程占用CPU的类型可以分为如下两种：</p><p>us:用户空间占用CPU百分比</p><p>sy:内核空间占用CPU百分比</p><p>在linux下可以通过top命令查看详细，示例如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Cpu(s):  0.2%us,  0.2%sy,  0.0%ni, 99.3%id,  0.3%wa,  0.0%hi,  0.0%si,  0.0%st</span><br></pre></td></tr></table></figure><p>CPU us高的原因主要是执行线程不需要任何挂起动作，且一直执行，导致CPU没有机会去调度执行其他的线程，我们上面分析的都属于这类情况。</p><p>CPU sy高的原因主要是线程的运行状态要经常切换，对于这种情况，常见的一种优化方法是减少线程数。</p><hr><h4 id="响应慢-低负载"><a href="#响应慢-低负载" class="headerlink" title="响应慢(低负载)"></a>响应慢(低负载)</h4><p>相应时间长，一般出现在高负载的机器上。如果在CPU的占用率很低，只有几个线程在消耗CPU的时间片，然而应用的响应时间却很长，我们首先要想到的是IO操作出现问题，至于到底是网络IO还是本地IO，我们就可以通过线程转储来定位。定位到位置之后，可以使用缓存或者减少IO操作来提升系统响应速度。</p><hr><h4 id="应用-服务宕机"><a href="#应用-服务宕机" class="headerlink" title="应用/服务宕机"></a>应用/服务宕机</h4><p>当一个应用活着却不能完成任何响应的时候，表明该服务已经宕机。</p><p>此时分析线程转储，会发现大量的线程在同一个操作中罢工了，没有任何一个线程能够完成自己的操作，从而导致该JVM再没有可用的线程。很多时候都是由于死锁导致的，当一个线程持有一个对象锁而不释放，而别的线程都在等待该锁的时候便会发生死锁现象。幸运的是JVM通常会检测死锁，通过工具分析转储能够更快的定位到原因。如果想避免复杂的死锁，我们需要尽可能减小同步块的大小，并且在进行资源的访问时设置合理的超时时间，避免死等现象。</p><p>综上所述，如果能够熟练分析线程转储，就可以在较短时间内识别，诊断，检测和解决生产环境的问题，这对于需要7*24服务的应用来说必备的技能之一。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近好几个同事都向我问起过java&lt;strong&gt;线程转储&lt;/strong&gt;方面的知识，都是些常见的问题，如：系统负载过高，系统吞吐率降低，系统挂起等等。这些问题的定位和解决都离不开&lt;strong&gt;线程转储&lt;/strong&gt;，因为这些问题在测试环境复现的难度较大，只有在生产环境进行&lt;strong&gt;ThreadDump&lt;/strong&gt;，才能快速定位问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;线程状态&quot;&gt;&lt;a href=&quot;#线程状态&quot; class=&quot;headerlink&quot; title=&quot;线程状态&quot;&gt;&lt;/a&gt;线程状态&lt;/h3&gt;&lt;p&gt;在具体分析线程转储数据之前，我们首先要明确线程的状态。&lt;strong&gt;java.lang.Thread.State&lt;/strong&gt;枚举类中定义了如下几种类型：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;NEW&lt;/strong&gt;：线程创建尚未启动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RUNNABLE&lt;/strong&gt;：包括操作系统线程状态中的Ready和Running，可能在等待时间片或者正在执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BLOCKED&lt;/strong&gt;：线程被阻塞。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WAITING&lt;/strong&gt;：不会分配CPU执行时间，直到别的线程显式的唤醒，否则无限期等待。LockSupport.park()，没有设置Timeout参数的Object.wait()和Thread.join()，会导致此现象。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TIMED_WAITING&lt;/strong&gt;：不会分配CPU执行时间，直到系统自动唤醒，不需要别的线程显示唤醒。Thread.sleep()，LockSupport.parkNanos()，LockSupport.parkUntil()，设置了超时时间的Object.wait()和Thread.join()，会让线程进入有限期等待。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TERMINATED&lt;/strong&gt;：线程执行结束。&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Thread" scheme="https://www.yangguo.info/tags/Thread/"/>
    
  </entry>
  
  <entry>
    <title>高性能编程术语</title>
    <link href="https://www.yangguo.info/2015/03/10/%E9%AB%98%E6%80%A7%E8%83%BD%E7%BC%96%E7%A8%8B%E6%9C%AF%E8%AF%AD/"/>
    <id>https://www.yangguo.info/2015/03/10/高性能编程术语/</id>
    <published>2015-03-10T07:01:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本篇文章,意在阐述并发，并行，分布式等高性能编程中的术语。请注意，本篇文章说将术语并没有标准定义，只是个人理解与定义。</p></blockquote><h3 id="Asynchronous-vs-Synchronous"><a href="#Asynchronous-vs-Synchronous" class="headerlink" title="Asynchronous vs. Synchronous"></a>Asynchronous vs. Synchronous</h3><p>一个方法的调用者不能做任何事情，直到该方法执行完成返回结果或者抛出异常，那么该方法就是<strong>同步</strong>的。与其相反，方法的调用者能够继续处理有限步骤的逻辑，直到被告知（Callback，Future，Message）该方法执行完成，然后调用者再回头处理调用结果，那个该方法就是<strong>异步</strong>的。</p><p>同步API可以通过<strong>阻塞</strong>来实现，但这也不是必须的。一个CPU密集型的任务可能会导致阻塞行为。在一般情况下，我们最好使用异步API，因为这样可以保证系统在非CPU密集导致的阻塞时，可以执行别的任务。说到异步编程，就不得不说<strong>Actor</strong>，它天生就是异步的，Actor能够处理别的任务，直到收到任务完成的<strong>message</strong>，而不必等待方法调用的实际交付。</p><a id="more"></a><hr><h3 id="Concurrency-vs-Parallelism"><a href="#Concurrency-vs-Parallelism" class="headerlink" title="Concurrency vs. Parallelism"></a>Concurrency vs. Parallelism</h3><p>许多人理解的<strong>并发</strong>和<strong>并行</strong>其实是存在偏差的，虽然它们的概念非常接近。这并不是吹毛求疵，因为概念的偏差，会导致我们对很多文档的理解出现歧义。并发指定的是两个或者多个事件可以在<strong>同一时间间隔</strong>发生，并行指的是两个或者多个事件可以在<strong>同一个时刻</strong>发生，从单核系统和多核系统角度来思考，便容易理解了。</p><hr><h3 id="Non-blocking-vs-Blocking"><a href="#Non-blocking-vs-Blocking" class="headerlink" title="Non-blocking vs. Blocking"></a>Non-blocking vs. Blocking</h3><p>当一个线程的延迟能够导致无限期的延迟别的线程，那么这就是我们所说的阻塞。一个非常好的例子便是同时只能被一个线程使用的互斥资源。假如一个线程无限期的持有一个资源（无限循环），在等待该资源的别的线程便永远不能执行。与此相反，非阻塞意味着没有线程能够无限期的阻塞别的线程。</p><p>非阻塞操作表现优于阻塞操作，随着包含阻塞操作的系统的逐渐运行，系统的质量是不能得到保障的。</p><hr><h3 id="Deadlock-vs-Starvation-vs-Live-lock"><a href="#Deadlock-vs-Starvation-vs-Live-lock" class="headerlink" title="Deadlock vs. Starvation vs. Live-lock"></a>Deadlock vs. Starvation vs. Live-lock</h3><p><strong>死锁</strong>是指两个或两个以上的进程（或线程）在执行过程中，因争夺资源而造成的一种互相等待的现象，若无外力作用，它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁，这些永远在互相等待的进程称为死锁进程（或线程）。死锁与阻塞是密切相关的，因为一个进程（或线程）能够导致别的线程运行不下去，从而导致阻塞。</p><p>死锁情况，没有一个进程（或线程）能够向前推进，而<strong>饥饿</strong>是部分进程（或线程）能够继续执行，另一部分不能够执行。典型的例子就是一个低级的调度算法，高优先级任务比低优先级任务先执行。如果高优先级任务源源不断的输入进来，而服务器资源只够处理高优先级任务，那么将会没有一个低优先级的任务完成，那么低优先级的任务就会被饿死。</p><p><strong>活锁</strong>与死锁类似，都是没有参与者能够向前推进。但不同之处就是，它不同于死锁由于等待资源而冻结，活锁可以认为是一种特殊的饥饿，活锁应该是一系列进程在轮询地等待某个不可能为真的条件为真。活锁的时候进程（或线程）是不会blocked，这会导致耗尽CPU资源。活锁分为单一实体活锁和协同导致的活锁。单一实体活锁，例如线程从队列中拿出一个任务来执行，如果任务执行失败，那么将任务重新加入队列，继续执行。假设任务总是执行失败，或者某种依赖的条件总是不满足，那么线程一直在繁忙却没有任何结果。协同活锁，例如两个人在窄路相遇，同时向一个方向避让，然后又同时向另一个方向避让，如此反复，结果谁也过不去。</p><hr><h3 id="Race-Condition"><a href="#Race-Condition" class="headerlink" title="Race Condition"></a>Race Condition</h3><p>多个线程或者进程在读写一个共享数据时结果依赖于它们执行的相对时间，这种情形叫做竞争。竞争条件发生在当多个进程或者线程在读写数据时，其最终的的结果依赖于多个进程的指令执行顺序。多个进程并发访问和操作同一数据且执行结果与访问的特定顺序有关，称为竞争条件。例如，假设两个进程P1和P2共享了变量a。在某一执行时刻，P1更新a为1，在另一时刻，P2更新a为2。因此两个任务竞争地写变量a。在这个例子中，竞争的“失败者”(最后更新的进程）决定了变量a的最终值。调试包含有竞争条件的程序是一件很头痛的事。大多数的测试运行结果都很好，但在极少数情况下会发生一些无法解释的奇怪现象。如果你的程序出现竞争条件，那么你的代码便会出现安全性问题，如果想对竞争条件有更多的了解，请<a href="http://www.ibm.com/developerworks/cn/linux/l-sprace.html" target="_blank" rel="noopener">点击此处</a></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;本篇文章,意在阐述并发，并行，分布式等高性能编程中的术语。请注意，本篇文章说将术语并没有标准定义，只是个人理解与定义。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;Asynchronous-vs-Synchronous&quot;&gt;&lt;a href=&quot;#Asynchronous-vs-Synchronous&quot; class=&quot;headerlink&quot; title=&quot;Asynchronous vs. Synchronous&quot;&gt;&lt;/a&gt;Asynchronous vs. Synchronous&lt;/h3&gt;&lt;p&gt;一个方法的调用者不能做任何事情，直到该方法执行完成返回结果或者抛出异常，那么该方法就是&lt;strong&gt;同步&lt;/strong&gt;的。与其相反，方法的调用者能够继续处理有限步骤的逻辑，直到被告知（Callback，Future，Message）该方法执行完成，然后调用者再回头处理调用结果，那个该方法就是&lt;strong&gt;异步&lt;/strong&gt;的。&lt;/p&gt;
&lt;p&gt;同步API可以通过&lt;strong&gt;阻塞&lt;/strong&gt;来实现，但这也不是必须的。一个CPU密集型的任务可能会导致阻塞行为。在一般情况下，我们最好使用异步API，因为这样可以保证系统在非CPU密集导致的阻塞时，可以执行别的任务。说到异步编程，就不得不说&lt;strong&gt;Actor&lt;/strong&gt;，它天生就是异步的，Actor能够处理别的任务，直到收到任务完成的&lt;strong&gt;message&lt;/strong&gt;，而不必等待方法调用的实际交付。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Thread" scheme="https://www.yangguo.info/tags/Thread/"/>
    
  </entry>
  
  <entry>
    <title>Squirrel使用</title>
    <link href="https://www.yangguo.info/2015/02/01/squirrel/"/>
    <id>https://www.yangguo.info/2015/02/01/squirrel/</id>
    <published>2015-02-01T12:01:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>java状态机squirrel的初级用法</p></blockquote><h3 id="Get-Starting"><a href="#Get-Starting" class="headerlink" title="Get Starting"></a>Get Starting</h3><p><strong>squirrel-foundation</strong>既支持流式<strong>API</strong>又支持声明式创建状态机，并且还允许用户以一种简单方式定义操作方法。</p><ul><li><strong>StateMachine</strong>接口需要以下4种泛型参数。<ul><li><strong>T</strong>代表实现的状态机类型。</li><li><strong>S</strong>代表实现的状态类型。</li><li><strong>E</strong>代表实现的事件类型。</li><li><strong>C</strong>代表实现的外部上下文类型。</li></ul></li></ul><h3 id="State-Machine-Builder"><a href="#State-Machine-Builder" class="headerlink" title="State Machine Builder"></a>State Machine Builder</h3><pre><code>* State machine builder用来定义状态机。StateMachineBuilder能够通过StateMachineBuilderFactory来创建。* StateMachineBuilder由`*TransitionBuilder (InternalTransitionBuilder / LocalTransitionBuilder / ExternalTransitionBuilder)`（用于状态之间转换）和`EntryExitActionBuilder`（用于构建操作入口或出口）的组成。* Internal state(内部状态)会被隐式创建，在transition或者state action创建的时候。* 所有的状态机实例会被同一个状态机builder创建，该builder共享一份结构化的数据，从而优化内存的使用。* 状态机builder在生成状态机的时候使用lazy模式。当builder创建第一个状态机实例时，包含时间消耗的状态机定义才会被创建。但是状态机定义生成之后，接下来的状态机创建将会非常快。一句话，状态机builder应该尽量重用。</code></pre><a id="more"></a><p>为了创建一个状态机，首先得创建一个状态机builder。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">StateMachineBuilder&lt;MyStateMachine, MyState, MyEvent, MyContext&gt; builder=</span><br><span class="line">StateMachineBuilderFactory.create(MyStateMachine.class, MyState.class, MyEvent.class, MyContext.class);</span><br></pre></td></tr></table></figure><p>状态机builder应该包含machine(T)，state(S)，event(E)和context(C)几个参数。</p><hr><h3 id="Fluent-API"><a href="#Fluent-API" class="headerlink" title="Fluent API"></a>Fluent API</h3><p>状态机builder创建之后，我们就能使用流失API来定义状态机的state/transition/action。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">builder.externalTransition().from(MyState.A).to(MyState.B).on(MyEvent.GoToB);</span><br></pre></td></tr></table></figure><p>创建一个状态从状态A到B，并且MyEvent.GoToB事件触发的<strong>external transition</strong>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">builder.internalTransition(TransitionPriority.HIGH).within(MyState.A).on(MyEvent.WithinA).perform(myAction);</span><br></pre></td></tr></table></figure><p>创建一个优先级为<strong>TransitionPriority.HIGH</strong>，内部状态为’A’，当事件为’WithinA’便执行’myAction’的状态机。<code>internal transition</code>意思是transition完成之后，没有状态退出和进入。优先级被用来覆盖继承来的状态机中的transition。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).when(</span><br><span class="line">    <span class="keyword">new</span> Condition&lt;MyContext&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isSatisfied</span><span class="params">(MyContext context)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> context!=<span class="keyword">null</span> &amp;&amp; context.getValue()&gt;<span class="number">80</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> String <span class="title">name</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"MyCondition"</span>;</span><br><span class="line">        &#125;</span><br><span class="line">&#125;).callMethod(<span class="string">"myInternalTransitionCall"</span>);</span><br></pre></td></tr></table></figure><p>当external context满足条件限制时，就创建一个从状态’C’到状态’D’事件为MyEvent.GoToD的<code>conditional transition</code>，然后调用的action method为”myInternalTransitionCall”。</p><p>用户也可以采用如下的方式，使用<a href="http://mvel.codehaus.org/" target="_blank" rel="noopener">MVEL</a>(一个强大的描述性语言)来描述条件。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).whenMvel(</span><br><span class="line">    <span class="string">"MyCondition:::(context!=null &amp;&amp; context.getValue()&gt;80)"</span>).callMethod(<span class="string">"myInternalTransitionCall"</span>);</span><br></pre></td></tr></table></figure><p><strong>Note:</strong>字符’:::’用来分离条件名称和条件表达式。’context’是预先定义好的指向当前上下文的对象。</p><p>上面的事例代码列出了进入action的各种state。</p><hr><h3 id="Method-Call-Action"><a href="#Method-Call-Action" class="headerlink" title="Method Call Action"></a>Method Call Action</h3><p>用户可以在定义transition或者state进入/退出时，定义匿名action。然而action代码会散落在许多地方难以维护。<br>而且，别的用户不能重写这些action。所以squirrel-foundation支持状态机方法调用动作与状态机类本身一起定义。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">StateMachineBuilder&lt;...&gt; builder = StateMachineBuilderFactory.create(</span><br><span class="line">    MyStateMachine.class, MyState.class, MyEvent.class, MyContext.class);</span><br><span class="line">builder.externalTransition().from(A).to(B).on(toB).callMethod(<span class="string">"fromAToB"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// All transition action method stays with state machine class</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyStateMachine</span>&lt;...&gt; <span class="keyword">extends</span> <span class="title">AbstractStateMachine</span>&lt;...&gt; </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">fromAToB</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// this method will be called during transition from "A" to "B" on event "toB"</span></span><br><span class="line">        <span class="comment">// the action method parameters types and order should match</span></span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此外,squirrel-foundation也支持通过<strong>Convention Over Configuration</strong>(约定优于配置)来定义方法调用。基本上,这意味着,如果状态机中声明的方法满足命名公约和参数惯例,它将被添加到transition action列表,也会在特定的阶段调用。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">transitFromAToBOnGoToB</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span></span><br></pre></td></tr></table></figure><p>方法名为<code>transitFrom[SourceStateName]To[TargetStateName]On[EventName]</code>，参数名为<code>[MyState, MyState, MyEvent, MyContext]</code>的方法会被添加到transition “A-(GoToB)-&gt;B”的action列表中。当状态机从’A’到’B’且触发的event为GoToB的时候，该方法会被调用。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">transitFromAnyToBOnGoToB</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span></span><br></pre></td></tr></table></figure><p>方法<code>transitFromAnyTo[TargetStateName]On[EventName]</code>会在任何状态通过event ‘GoToB’向状态’B’转化的时候调用。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">exitA</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span></span><br></pre></td></tr></table></figure><p>方法<strong>exit[StateName]</strong>会在退出状态’A’的时候被调用。同理，<strong>entry[StateName]</strong>, <strong>beforeExitAny/afterExitAny</strong>和<strong>beforeEntryAny/afterEntryAny</strong>的意思也就不难理解了。</p><p><strong>还支持的命名模式：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">transitFrom[fromStateName]To[toStateName]On[eventName]When[conditionName]  </span><br><span class="line">transitFrom[fromStateName]To[toStateName]On[eventName]  </span><br><span class="line">transitFromAnyTo[toStateName]On[eventName]  </span><br><span class="line">transitFrom[fromStateName]ToAnyOn[eventName]  </span><br><span class="line">transitFrom[fromStateName]To[toStateName]          </span><br><span class="line">on[eventName]</span><br></pre></td></tr></table></figure><p>上述这些方法规范还提供了类aop功能，squirrel在任何粒度提供了内置的灵活扩展能力。<br>如果需要获取更多信息，请参考测试用例<code>org.squirrelframework.foundation.fsm.ExtensionMethodCallTest</code>。<br>0.3.1版本后，还有另一种方法是通过流式API来定义这些类aop扩展方法(谢谢<a href="https://github.com/vittali" target="_blank" rel="noopener">vittali</a>的建议)，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// since 0.3.1</span></span><br><span class="line"><span class="comment">// the same effect as add method transitFromAnyToCOnToC in your state machine</span></span><br><span class="line">builder.transit().fromAny().to(<span class="string">"C"</span>).on(<span class="string">"ToC"</span>).callMethod(<span class="string">"fromAnyToC"</span>);</span><br><span class="line"><span class="comment">// the same effect as add method transitFromBToAnyOnToC in your state machine</span></span><br><span class="line">builder.transit().from(<span class="string">"B"</span>).toAny().on(<span class="string">"ToC"</span>).callMethod(<span class="string">"fromBToAny"</span>);</span><br><span class="line"><span class="comment">// the same effect as add method transitFromBToAny in your state machine</span></span><br><span class="line">builder.transit().from(<span class="string">"B"</span>).toAny().onAny().callMethod(<span class="string">"fromBToAny"</span>);</span><br></pre></td></tr></table></figure><p>或者使用注释，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// since 0.3.1</span></span><br><span class="line"><span class="meta">@Transitions</span>(&#123;</span><br><span class="line">     <span class="meta">@Transit</span>(from=<span class="string">"B"</span>, to=<span class="string">"E"</span>, on=<span class="string">"*"</span>,   callMethod=<span class="string">"fromBToEOnAny"</span>),</span><br><span class="line">     <span class="meta">@Transit</span>(from=<span class="string">"*"</span>, to=<span class="string">"E"</span>, on=<span class="string">"ToE"</span>, callMethod=<span class="string">"fromAnyToEOnToE"</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><strong>Note:</strong>这些action方法会附加在已经存在且匹配上的transition上，但不创建新的transition。<br>从0.3.4，使用下面的API，可以一次定义多个transition。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// transitions(A-&gt;B@A2B=&gt;a2b, A-&gt;C@A2C=&gt;a2c, A-&gt;D@A2D) will be defined at once</span></span><br><span class="line">builder.transitions().from(State._A).toAmong(State.B, State.C, State.D).</span><br><span class="line">        onEach(Event.A2B, Event.A2C, Event.A2D).callMethod(<span class="string">"a2b|a2c|_"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// transitions(A-&gt;_A@A2ANY=&gt;DecisionMaker, _A-&gt;A@ANY2A) will be defined at once</span></span><br><span class="line">builder.localTransitions().between(State.A).and(State._A).</span><br><span class="line">        onMutual(Event.A2ANY, Event.ANY2A).</span><br><span class="line">        perform( Lists.newArrayList(<span class="keyword">new</span> DecisionMaker(<span class="string">"SomeLocalState"</span>), <span class="keyword">null</span>) );</span><br></pre></td></tr></table></figure><p>更多的信息可以查看<code>org.squirrelframework.foundation.fsm.samples.DecisionStateSampleTest</code>。</p><hr><h3 id="Declarative-Annotation"><a href="#Declarative-Annotation" class="headerlink" title="Declarative Annotation"></a>Declarative Annotation</h3><p>该方式提供了一种声明式的方式来定义和扩展状态机。例子如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@States</span>(&#123;</span><br><span class="line">    <span class="meta">@State</span>(name=<span class="string">"A"</span>, entryCallMethod=<span class="string">"entryStateA"</span>, exitCallMethod=<span class="string">"exitStateA"</span>),</span><br><span class="line">    <span class="meta">@State</span>(name=<span class="string">"B"</span>, entryCallMethod=<span class="string">"entryStateB"</span>, exitCallMethod=<span class="string">"exitStateB"</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="meta">@Transitions</span>(&#123;</span><br><span class="line">    <span class="meta">@Transit</span>(from=<span class="string">"A"</span>, to=<span class="string">"B"</span>, on=<span class="string">"GoToB"</span>, callMethod=<span class="string">"stateAToStateBOnGotoB"</span>),</span><br><span class="line">    <span class="meta">@Transit</span>(from=<span class="string">"A"</span>, to=<span class="string">"A"</span>, on=<span class="string">"WithinA"</span>, callMethod=<span class="string">"stateAToStateAOnWithinA"</span>, type=TransitionType.INTERNAL)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">MyStateMachine</span> <span class="keyword">extends</span> <span class="title">StateMachine</span>&lt;<span class="title">MyStateMachine</span>, <span class="title">MyState</span>, <span class="title">MyEvent</span>, <span class="title">MyContext</span>&gt; </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">entryStateA</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">stateAToStateBOnGotoB</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span></span><br><span class="line"><span class="function">    <span class="keyword">void</span> <span class="title">stateAToStateAOnWithinA</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span></span><br><span class="line"><span class="function">    <span class="keyword">void</span> <span class="title">exitStateA</span><span class="params">(MyState from, MyState to, MyEvent event, MyContext context)</span></span>;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注解既可以定义在状态机的实现类上也可以定义在状态机需要实现的任何接口上面。注解也可以混合着流式API使用，这意味着流式API定义的状态机也可以使用注解。（有一件事情需要注意，接口中定义的方法必须要是public的，这意味着对于方法的调用者来说，需要调用的方法在实现类中必须是public的。）</p><hr><h3 id="Converters"><a href="#Converters" class="headerlink" title="Converters"></a>Converters</h3><p>为了在注解@State和@Transit中声明状态，用户需要为state（S）和event（E）类型实现相应的转换器。该转换器必须实现Converter<t>接口，该转换器可以把state/event和String进行互相转换。</t></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Converter</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">SquirrelComponent</span> </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Convert object to string.</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> obj converted object</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> string description of object</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="function">String <span class="title">convertToString</span><span class="params">(T obj)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Convert string to object.</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> name name of the object</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> converted object</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="function">T <span class="title">convertFromString</span><span class="params">(String name)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后再将这些转换器注册到ConverterProvider中，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ConverterProvider.INSTANCE.register(MyEvent.class, <span class="keyword">new</span> MyEventConverter());</span><br><span class="line">ConverterProvider.INSTANCE.register(MyState.class, <span class="keyword">new</span> MyStateConverter());</span><br></pre></td></tr></table></figure><p><strong>注意：</strong>如果你仅使用流式API来定义状态机，那么不需要实现相应的转换器。并且如果Event和State是String和Enumeration类型，在大多数情况下你也不需要实现和注册转换器。</p><hr><h3 id="New-State-Machine-Instance"><a href="#New-State-Machine-Instance" class="headerlink" title="New State Machine Instance"></a>New State Machine Instance</h3><p>用户定义完状态机的行为之后，就需要通过builder来创建一个状态机实例。注意，一旦通过builder创建好状态机，该builder就不能再用于定义任何包含新元素的状态机。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">T <span class="title">newStateMachine</span><span class="params">(S initialStateId, Object... extraParams)</span></span>;</span><br></pre></td></tr></table></figure><p>为了通过状态机builder创建状态机实例，你需要传递以下的参数。</p><pre><code>i. initialStateId:开始时，状态机的初始状态。ii. extraParams:创建状态机时需要的额外参数。如果不需要额外的参数，设置为&quot;new Object[0]&quot;。    a.如果在创建状态机实例时传递了额外的参数，请确保在创建状态机builder时定义了StateMachineBuilderFactory</code></pre><p>假如不需要传递额外参数，用户可以简单的调用<strong>T newStateMachine(S initialStateId)</strong>来创建状态机实例。</p><p>通过状态机builder创建一个新的状态机。（该例子，不需要传递额外的参数。）</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyStateMachine stateMachine = builder.newStateMachine(MyState.Initial);</span><br></pre></td></tr></table></figure><hr><h3 id="Trigger-Transitions"><a href="#Trigger-Transitions" class="headerlink" title="Trigger Transitions"></a>Trigger Transitions</h3><p>状态机创建之后，用户可以发送event以及context，在状态机内部触发transition。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">stateMachine.fire(MyEvent.Prepare, <span class="keyword">new</span> MyContext(<span class="string">"Testing"</span>));</span><br></pre></td></tr></table></figure><h3 id="Untyped-State-Machine"><a href="#Untyped-State-Machine" class="headerlink" title="Untyped State Machine"></a>Untyped State Machine</h3><p>UntypedStateMachine的目的是为了简化状态机的使用，和避免由于太多的泛型（如：StateMachine&lt;T,S,E,C&gt;）造成的代码难以阅读的情况，并且仍然能够保证transition重要部分的类型安全。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> TestEvent &#123;</span><br><span class="line">    toA, toB, toC, toD</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transitions</span>(&#123;</span><br><span class="line">    <span class="meta">@Transit</span>(from=<span class="string">"A"</span>, to=<span class="string">"B"</span>, on=<span class="string">"toB"</span>, callMethod=<span class="string">"fromAToB"</span>),</span><br><span class="line">    <span class="meta">@Transit</span>(from=<span class="string">"B"</span>, to=<span class="string">"C"</span>, on=<span class="string">"toC"</span>),</span><br><span class="line">    <span class="meta">@Transit</span>(from=<span class="string">"C"</span>, to=<span class="string">"D"</span>, on=<span class="string">"toD"</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="meta">@StateMachineParameters</span>(stateType=String.class, eventType=TestEvent.class, contextType=Integer.class)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UntypedStateMachineSample</span> <span class="keyword">extends</span> <span class="title">AbstractUntypedStateMachine</span> </span>&#123;</span><br><span class="line">    <span class="comment">// No need to specify constructor anymore since 0.2.9  </span></span><br><span class="line">    <span class="comment">// protected UntypedStateMachineSample(ImmutableUntypedState initialState,</span></span><br><span class="line">    <span class="comment">//  Map&lt;Object, ImmutableUntypedState&gt; states) &#123;</span></span><br><span class="line">    <span class="comment">//    super(initialState, states);</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">fromAToB</span><span class="params">(String from, String to, TestEvent event, Integer context)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// transition action still type safe ...</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">transitFromDToAOntoA</span><span class="params">(String from, String to, TestEvent event, Integer context)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// transition action still type safe ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(</span><br><span class="line">    UntypedStateMachineSample.class);</span><br><span class="line"><span class="comment">// state machine builder not type safe anymore</span></span><br><span class="line">builder.externalTransition().from(<span class="string">"D"</span>).to(<span class="string">"A"</span>).on(TestEvent.toA);</span><br><span class="line">UntypedStateMachine fsm = builder.newStateMachine(<span class="string">"A"</span>);</span><br></pre></td></tr></table></figure><p>为了创建一个UntypedStateMachine，首先需要通过StateMachineBuilderFactory创建一个UntypedStateMachineBuilder。StateMachineBuilderFactory仅需要一个参数，该参数就是需要创建UntypedStateMachineBuilder的类的名称。注解@StateMachineParameters用来声明状态机泛型参数类型。AbstractUntypedStateMachine是任何无状态的状态机的基类。</p><h3 id="Context-Insensitive-State-Machine"><a href="#Context-Insensitive-State-Machine" class="headerlink" title="Context Insensitive State Machine"></a>Context Insensitive State Machine</h3><p>有时状态机根本不关心上下文，意思是transition只取决于事件。这种情况下用户可以使用上下文不敏感的状态机来简化方法调用的参数。</p><p>声明上下文不敏感的状态机非常简单。用户只需要在状态机实现类上添加注解@ContextInsensitive。之后，在transition方法的参数中可以忽略context参数。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ContextInsensitive</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ATMStateMachine</span> <span class="keyword">extends</span> <span class="title">AbstractStateMachine</span>&lt;<span class="title">ATMStateMachine</span>, <span class="title">ATMState</span>, <span class="title">String</span>, <span class="title">Void</span>&gt; </span>&#123;</span><br><span class="line">    <span class="comment">// no need to add context parameter here anymore</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">transitFromIdleToLoadingOnConnected</span><span class="params">(ATMState from, ATMState to, String event)</span> </span>&#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">entryLoading</span><span class="params">(ATMState from, ATMState to, String event)</span> </span>&#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Transition-Exception-Handling"><a href="#Transition-Exception-Handling" class="headerlink" title="Transition Exception Handling"></a>Transition Exception Handling</h3><p>当状态转换过程中出现异常，已执行的action列表将失效并且状态机会进入error状态，意思就是状态机实例不会再处理任何event。假如用户继续向状态机发送event，便会抛出<strong>IllegalStateException</strong>异常。所有状态转换过程中发生的异常，包括action执行和外部listener调用，会被包装成TransitionException（未检查异常）。目前，默认的异常处理策略非常简单并且粗暴的连续抛出异常，可以参阅<code>AbstractStateMachine.afterTransitionCausedException</code>方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">afterTransitionCausedException</span><span class="params">(...)</span> </span>&#123; <span class="keyword">throw</span> e; &#125;</span><br></pre></td></tr></table></figure><p>假如状态机可以从该异常恢复，用户可以扩展<code>afterTransitionCausedException</code>方法，在该方法中添加相应的恢复逻辑。<strong>注意</strong>，在最后别忘记将状态设置为正常。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">afterTransitionCausedException</span><span class="params">(Object fromState, Object toState, Object event, Object context)</span> </span>&#123;</span><br><span class="line">    Throwable targeException = getLastException().getTargetException();</span><br><span class="line">    <span class="comment">// recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'</span></span><br><span class="line">    <span class="keyword">if</span>(targeException <span class="keyword">instanceof</span> IllegalArgumentException &amp;&amp;</span><br><span class="line">            fromState.equals(<span class="string">"A"</span>) &amp;&amp; toState.equals(<span class="string">"B"</span>) &amp;&amp; event.equals(<span class="string">"ToB"</span>)) &#123;</span><br><span class="line">        <span class="comment">// do some error clean up job here</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        <span class="comment">// after recovered from this exception, reset the state machine status back to normal</span></span><br><span class="line">        setStatus(StateMachineStatus.IDLE);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>(...) &#123;</span><br><span class="line">        <span class="comment">// recover from other exception ...</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">super</span>.afterTransitionCausedException(fromState, toState, event, context);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Define-Hierarchical-State"><a href="#Define-Hierarchical-State" class="headerlink" title="Define Hierarchical State"></a>Define Hierarchical State</h3><p>层级状态可以包含嵌套的状态。子状态自己可以包含嵌套的子状态和嵌套任意深度的逻辑。当一个嵌套状态处于活跃，有且仅有一个子状态处于活跃。分层状态可以通过API或者注解来定义。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">defineSequentialStatesOn</span><span class="params">(S parentStateId, S... childStateIds)</span></span>;</span><br></pre></td></tr></table></figure><p><code>builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA)</code>定义了两个子状态“BinA”和“CinA”从属于父母状态“A”，第一个定义的子状态”A”也就是初始状态。同一个层级状态也可以通过注解来定义，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@States</span>(&#123;</span><br><span class="line">    <span class="meta">@State</span>(name=<span class="string">"A"</span>, entryMethodCall=<span class="string">"entryA"</span>, exitMethodCall=<span class="string">"exitA"</span>),</span><br><span class="line">    <span class="meta">@State</span>(parent=<span class="string">"A"</span>, name=<span class="string">"BinA"</span>, entryMethodCall=<span class="string">"entryBinA"</span>, exitMethodCall=<span class="string">"exitBinA"</span>, initialState=<span class="keyword">true</span>),</span><br><span class="line">    <span class="meta">@State</span>(parent=<span class="string">"A"</span>, name=<span class="string">"CinA"</span>, entryMethodCall=<span class="string">"entryCinA"</span>, exitMethodCall=<span class="string">"exitCinA"</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="Define-Parallel-State"><a href="#Define-Parallel-State" class="headerlink" title="Define Parallel State"></a>Define Parallel State</h3><p>并行状态是将孩子状态封装到一个集合中，当父元素激活时，并行状态便激活。并行状态既可以通过API定义又可以通过注解定义。例如：<br><img src="/images/Parallel_State.png" alt="Parallel_State"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// defines two region states "RegionState1" and "RegionState2" under parent parallel state "Root"</span></span><br><span class="line">builder.defineParallelStatesOn(MyState.Root, MyState.RegionState1, MyState.RegionState2);</span><br><span class="line"></span><br><span class="line">builder.defineSequentialStatesOn(MyState.RegionState1, MyState.State11, MyState.State12);</span><br><span class="line">builder.externalTransition().from(MyState.State11).to(MyState.State12).on(MyEvent.Event1);</span><br><span class="line"></span><br><span class="line">builder.defineSequentialStatesOn(MyState.RegionState2, MyState.State21, MyState.State22);</span><br><span class="line">builder.externalTransition().from(MyState.State21).to(MyState.State22).on(MyEvent.Event2);</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@States</span>(&#123;</span><br><span class="line">    <span class="meta">@State</span>(name=<span class="string">"Root"</span>, entryCallMethod=<span class="string">"enterRoot"</span>, exitCallMethod=<span class="string">"exitRoot"</span>, compositeType=StateCompositeType.PARALLEL),</span><br><span class="line">    <span class="meta">@State</span>(parent=<span class="string">"Root"</span>, name=<span class="string">"RegionState1"</span>, entryCallMethod=<span class="string">"enterRegionState1"</span>, exitCallMethod=<span class="string">"exitRegionState1"</span>),</span><br><span class="line">    <span class="meta">@State</span>(parent=<span class="string">"Root"</span>, name=<span class="string">"RegionState2"</span>, entryCallMethod=<span class="string">"enterRegionState2"</span>, exitCallMethod=<span class="string">"exitRegionState2"</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>从并行状态获取当前子状态</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">stateMachine.getSubStatesOn(MyState.Root); <span class="comment">// return list of current sub states of parallel state</span></span><br></pre></td></tr></table></figure><p>当所有的并行状态到达最终状态，一个<strong>Finish</strong>上下文的event会被发送。</p><h3 id="Define-Context-Event"><a href="#Define-Context-Event" class="headerlink" title="Define Context Event"></a>Define Context Event</h3><p>状态机中的上下文事件都有预定义的上下文。squirrel-foundation为不同使用实例定义了3种上下文事件。</p><p><strong>Start/Terminate Event</strong>：当状态机启动/终止时，会使用到定义好的start/terminate事件。因此用户可以辨认action触发时的调用，例如。当状态机启动和进入起始状态，用户可以知道被start事件激活的action调用。</p><p><strong>Finish Event</strong>：当所有的并行状态抵达最终状态，finish event会被自动发射。用户可以在finish event上定义如下如下的转换。</p><p>定义上下文事件，也有两种方法，注解获取builder API。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ContextEvent</span>(finishEvent=<span class="string">"Finish"</span>)</span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ParallelStateMachine</span> <span class="keyword">extends</span> <span class="title">AbstractStateMachine</span>&lt;...&gt; </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">StateMachineBuilder&lt;...&gt; builder = StateMachineBuilderFactory.create(...);</span><br><span class="line">...</span><br><span class="line">builder.defineFinishEvent(HEvent.Start);</span><br><span class="line">builder.defineTerminateEvent(HEvent.Terminate);</span><br><span class="line">builder.defineStartEvent(HEvent.Finish);</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;java状态机squirrel的初级用法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;Get-Starting&quot;&gt;&lt;a href=&quot;#Get-Starting&quot; class=&quot;headerlink&quot; title=&quot;Get Starting&quot;&gt;&lt;/a&gt;Get Starting&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;squirrel-foundation&lt;/strong&gt;既支持流式&lt;strong&gt;API&lt;/strong&gt;又支持声明式创建状态机，并且还允许用户以一种简单方式定义操作方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StateMachine&lt;/strong&gt;接口需要以下4种泛型参数。&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;T&lt;/strong&gt;代表实现的状态机类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;S&lt;/strong&gt;代表实现的状态类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt;代表实现的事件类型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C&lt;/strong&gt;代表实现的外部上下文类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;State-Machine-Builder&quot;&gt;&lt;a href=&quot;#State-Machine-Builder&quot; class=&quot;headerlink&quot; title=&quot;State Machine Builder&quot;&gt;&lt;/a&gt;State Machine Builder&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;* State machine builder用来定义状态机。StateMachineBuilder能够通过StateMachineBuilderFactory来创建。
* StateMachineBuilder由`*TransitionBuilder (InternalTransitionBuilder / LocalTransitionBuilder / ExternalTransitionBuilder)`（用于状态之间转换）和`EntryExitActionBuilder`（用于构建操作入口或出口）的组成。
* Internal state(内部状态)会被隐式创建，在transition或者state action创建的时候。
* 所有的状态机实例会被同一个状态机builder创建，该builder共享一份结构化的数据，从而优化内存的使用。
* 状态机builder在生成状态机的时候使用lazy模式。当builder创建第一个状态机实例时，包含时间消耗的状态机定义才会被创建。但是状态机定义生成之后，接下来的状态机创建将会非常快。一句话，状态机builder应该尽量重用。
&lt;/code&gt;&lt;/pre&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="状态机" scheme="https://www.yangguo.info/tags/%E7%8A%B6%E6%80%81%E6%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>编译原理之Javacc使用</title>
    <link href="https://www.yangguo.info/2014/12/13/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86-Javacc%E4%BD%BF%E7%94%A8/"/>
    <id>https://www.yangguo.info/2014/12/13/编译原理-Javacc使用/</id>
    <published>2014-12-13T15:14:34.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近由于需要解析Eterm指令结果，对于这种不遵循标准格式（XML/JSON/HTML）的文本，我又不想大量的堆叠大量的正则表达式，因此借助Javacc来解决我的问题。</p></blockquote><h3 id="编译知识"><a href="#编译知识" class="headerlink" title="编译知识"></a>编译知识</h3><p>对于码农来说，应该不需要再解释编译这回事了。一般我们将语言分为编译型语言和解释型语言，但是不管是哪种语言，都少不了<strong>词法分析</strong>、<strong>语法分析</strong>。</p><hr><h3 id="词法分析（lexing）"><a href="#词法分析（lexing）" class="headerlink" title="词法分析（lexing）"></a>词法分析（lexing）</h3><p>词法分析就是将文本分解成token，token就是具有特殊含义的原子单位，如果语言的保留字、标点符号、数字、字符串，当然包含空白等等，只不过有些如空白会在词法分析时忽略掉。</p><a id="more"></a><hr><h3 id="语法分析（paring）"><a href="#语法分析（paring）" class="headerlink" title="语法分析（paring）"></a>语法分析（paring）</h3><p>它的作用是进行语法检查、并构建由输入的token组成的数据结构（一般是语法分析树、抽象语法树等层次化的数据结构）。语法分析器通常使用一个独立的词法分析器从输入字符流中分离出一个个的token，并将token流作为其输入。</p><p>语法分析器的任务主要是确定是否可以以及如何从语法的起始符号推导出输入符号串（输入文本），主要可以通过两种方式完成：</p><ol><li>自顶向下分析：根据形式语法规则，在语法分析树的<strong>自顶向下</strong>展开中搜索输入符号串可能的<strong>最左推导</strong>。单词按从左到右的顺序依次使用。它对应<strong>LL分析器</strong>，由于Javacc使用<strong>LL分析器</strong>，所以它是本文默认讨论的分析器。</li><li>自底向上分析：语法分析器从现有的输入符号串开始，尝试将其根据给定的形式语法规则进行改写，最终改写为语法的起始符号，它对应<strong>LR分析器</strong>。</li></ol><p>此处有必要将最左推导（规范规约）和最右推导说明下，例子如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">文法：</span><br><span class="line">S---&gt;AB</span><br><span class="line">A---&gt;a|t</span><br><span class="line">B----&gt;+CD</span><br><span class="line">C---&gt;a</span><br><span class="line">D----&gt;a</span><br><span class="line">最右推导：</span><br><span class="line">S---&gt;AB----&gt;A+CD---&gt;A+Ca----&gt;A+aa-----&gt;a+aa</span><br><span class="line">最左推导：</span><br><span class="line">S----&gt;AB-----&gt;aB---&gt;a+CD---&gt;a+aD-----&gt;a+aa</span><br></pre></td></tr></table></figure><p>讲完分析方式和推导方式，那么接下来，我介绍<a href="http://zh.wikipedia.org/wiki/%E6%89%A9%E5%B1%95%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F" target="_blank" rel="noopener">EBNF</a>(扩展巴科斯范式),它是描述语言的<strong>元语法</strong>。</p><p>代码，是由<a href="http://zh.wikipedia.org/wiki/%E7%B5%82%E7%B5%90%E7%AC%A6%E8%88%87%E9%9D%9E%E7%BB%88%E7%B5%90%E7%AC%A6" target="_blank" rel="noopener">终结符</a>即可视字符、数字、标点符号、空白等字符组成的 计算机源代码。<strong>EBNF</strong>定义了把<strong>终结符</strong>指派到<strong>非终结符</strong>的产生规则。</p><h4 id="扩展巴科斯范式的一些规则"><a href="#扩展巴科斯范式的一些规则" class="headerlink" title="扩展巴科斯范式的一些规则"></a>扩展巴科斯范式的一些规则</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">digit excluding zero = &quot;1&quot; | &quot;2&quot; | &quot;3&quot; | &quot;4&quot; | &quot;5&quot; | &quot;6&quot; | &quot;7&quot; | &quot;8&quot; | &quot;9&quot; ;</span><br><span class="line">digit                = &quot;0&quot; | digit excluding zero ;</span><br><span class="line"></span><br><span class="line">这个产生规则定义了在这个指派的左端的非终结符digit。竖杠表示可供选择，而终结符被引号包围，最后跟着分号作为终止字符，所以digit可以是一个0-9中的任意一个数。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">natural number = digit excluding zero , &#123; digit &#125; ;</span><br><span class="line"></span><br><span class="line">可以省略或重复的表达式可以通过花括号 &#123; ... &#125; 表示。在这种情况下，字符串 1，2，...，10，...，12345，... 都是正确的表达式。要表示这种情况，于花括号内设立的所有东西可以重复任何次，包括根本不出现。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">integer = &quot;0&quot; | [ &quot;-&quot; ] , natural number ;</span><br><span class="line"></span><br><span class="line">可选项可以通过方括号 [ ... ] 表示，所以integer是一个零(0)或可能前导可选的负号的一个自然数。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">twelve                          = &quot;1&quot; , &quot;2&quot; ;</span><br><span class="line">two hundred one                 = &quot;2&quot; , &quot;0&quot; , &quot;1&quot; ;</span><br><span class="line">three hundred twelve            = &quot;3&quot; , twelve ;</span><br><span class="line">twelve thousand two hundred one = twelve , two hundred one ;</span><br><span class="line"></span><br><span class="line">产生规则还可以包括由逗号分隔的一序列终结符或非终结符。</span><br></pre></td></tr></table></figure><h3 id="JavaCC"><a href="#JavaCC" class="headerlink" title="JavaCC"></a>JavaCC</h3><p>为了简化基于<strong>Java</strong>语言的词法分析器或者语法分析器的开发，Sun公司的开发人员开发了JavaCC(Java Compiler Compiler)。JavaCC是一个基于<strong>Java</strong>语言的分析器的自动生成器。用户只要按照JavaCC的语法规范编写JavaCC的源文件，然后使用JavaCC的编译器编译，就能够生成基于Java语言的某种特定语言的分析器。JavaCC允许我们用类似EBNF的方式来定义语法规则，这样就使得从EBNF语法规则到JavaCC格式的语法规则的转换很容易。这也是我前面讲<strong>EBNF的原因</strong>，并且JavaCC已经成为最受欢迎的Java解析器创建工具，它还自带了的预先设定好的JavaCC语法，以作为应用起点，从而使使用难度进一步降低。</p><p>javaCC抛弃了java中的“&lt;&lt;”，“&gt;&gt;”，“&gt;&gt;&gt;”，“&lt;&lt;=”，“=&gt;&gt;”，“&gt;&gt;&gt;=”被javacc从token列表中抛弃，加入了自己的保留字。</p><table><thead><tr><th>TOKEN</th><th>TOKEN_MGR_DECLS</th><th>SPECIAL_TOKEN</th><th>SKIP</th></tr></thead><tbody><tr><td><strong>PARSER_END</strong></td><td><strong>PARSER_BEGIN</strong></td><td><strong>MORE</strong></td><td><strong>LOOKAHEAD</strong></td></tr><tr><td><strong>JAVACODE</strong></td><td><strong>IGNORE_CASE</strong></td><td><strong>EOF</strong></td></tr></tbody></table><h3 id="JavaCC语法："><a href="#JavaCC语法：" class="headerlink" title="JavaCC语法："></a>JavaCC语法：</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">javacc_options</span><br><span class="line">&quot;PARSER_BEGIN&quot; &quot;(&quot; &lt;IDENTIFIER&gt; &quot;)&quot;</span><br><span class="line">java_compilation_unit</span><br><span class="line">&quot;PARSER_END&quot; &quot;(&quot; &lt;IDENTIFIER&gt; &quot;)&quot;</span><br><span class="line">( production )*</span><br><span class="line">&lt;EOF&gt;</span><br></pre></td></tr></table></figure><p>JavaCC语法文件以options列表开始，这个不是必须的，因为这些选项都有默认值。</p><h4 id="常见的选项如下："><a href="#常见的选项如下：" class="headerlink" title="常见的选项如下："></a>常见的选项如下：</h4><ol><li><strong>LOOKAHEAD</strong>：设置在解析过程中面临choice point可以look ahead的token数量。缺省的值是1，这个值越小，解析的速度就越快。这个设置可能会被特定产生式内部的声明给覆盖掉。</li><li><strong>CHOICE_AMBIGUITY_CHECK</strong>：这个选项的值是数值型，缺省的值是2。这是在形如”A | B | …”这种选择点出现文法二义性的时候，作为参考的token的数量。例如，A和B有着两个相同的开始token序列，但是从第3个token开始就不一样了（在这个例子中假设这个option的值设置成3），这个时候javacc就会告诉你使用值为3的lookahead就可以消除二义性。如果A和B有着3个共同Token的前缀，那么javacc就是告诉你需lookahead 3个或者更多的token了。增加这个选项的值可以以解析的速度换了更详细的二义性信息。但是，对于复杂的语法，比如java，增加这个数字就会导致增加太多的解析时间。</li><li><strong>OTHER_AMBIGUITY_CHECK</strong>：这是一个数值型的选项，默认值是1。这个设置了在其他类型的二义性发生的时候选择lookahead的token数量。例如”(A)*”, “(A)+”, and “(A)?”形式的二义性。这个比上choice checking更加耗时（也就是上面那个选项），所以其默认值是1而不是2。</li><li><strong>STATIC</strong>：boolean类型的选项，默认值是true。如果为true，那么所有在parser和token manager中生成的方法和属性，都会被声明成static。这样做会导致只允许一个parser对象被创建，但是可以提高parser的性能。如果static为true，那么在需要多parser对象的时候，需要调用ReInit方法去重新初始化parser。如果static为false，就可以通过new操作符来创建对象了，然后他们可以在多个线程中同时被使用。这个得非常注意，特别是使用javacc来并行处理一些业务逻辑。</li><li><strong>DEBUG_LOOKAHEAD</strong>：boolean类型的选项，初始值是false。设置为true的时候，可以显示在paraser执行lookahead 的时候把动作打印出来。</li><li><strong>DEBUG_TOKEN_MANAGER</strong>：boolean类型的选项，默认值为false。用于打开token manager的debug日志。当选项为true的时候，token manager就会打印出其所有的动作。这中类型的日志非常多，所有应该在你面对一个文法错误，但是又不知道是怎么回事的时候才被使用。一般来说，你只需要看日志的最后几行应该就能够定位到错误了。</li><li><strong>ERROR_REPORTING</strong>：boolean类型的选项，默认值为true。设置为false的时候，会导致解析错误的信息以更简洁的形式提供。只有在为了提高效率的情况下才需要把这个选项设置为false。</li><li><strong>JAVA_UNICODE_ESCAPE</strong>：boolean类型的选项，默认值为false。当设置为true的时候，生成的解析器会在把字符串传递给token manager之前处理其中的unicode字符。否则形如\u…的字符串是不会被特殊处理的。如果USER_TOKEN_MANAGER, USER_CHAR_STREAM选项设置为true后，这个选项将会被忽略。</li><li><strong>UNICODE_INPUT</strong>：boolean类型的选项，默认值是false。设置为true的时候，生成的解析器将使用一个读取unicode文件的输入流，默认情况下是一个读取ascii文件的输入流。在选项USER_TOKEN_MANAGER，USER_CHAR_STREAM被设置成true的时候，这个选项会被忽略。</li><li><strong>IGNORE_CASE</strong>：boolean类型的选项，默认值是false。当设置成true的时候，生成的token manager会忽略输入的文件和token的声明中的大小写。在书写类似如html的语言的时候，这个选项就会非常有用了，当然你也可以定制化IGNORE_CASE。</li><li><strong>USER_TOKEN_MANAGER</strong>：boolean类型的选项，默认值是false。默认的情况下，生成的token manager仅仅工作在特定的token范围内。如果这个选项设置为true，生成的解析器会接受来自于任何实现了TokenManager对象。</li><li><strong>SUPPORT_CLASS_VISIBILITY_PUBLIC</strong>：boolean类型的选项，默认值是true。在默认的情况下，生成的支撑类（例如Token.java, ParseException.java等等）是具有Public 可见性的对象，如果这个选项被设置成false，那么这些类的可见性将会被设置成package-private级别。</li><li><strong>USER_CHAR_STREAM</strong>：boolean类型的选项，默认值是true。在默认的情况下，按照<strong>JAVA_UNICODE_ESCAPE</strong>和<strong>UNICODE_INPUT</strong>选项生成一个字符stream reader。生成的token manager会从这个reader中接受字符串。如果这个选项被设置成true，token manager将从任意一个实现了”CharStream.java”的reader中接受字符串。这个文件可以在解析器生成的文件夹下。在<strong>USER_TOKEN_MANAGER</strong>选项设置成true的时候，这个选项会被忽略。</li><li><strong>BUILD_PARSER</strong>：boolean类型的选项，默认值为true。在默认的情况下，javacc会生成一个解析器对象，例如上面提到的MyParser.java文件。在这个选项被设置成false的时候，解析器将不会被生成。一般情况下，如果仅仅需要生成token manager，那个就可以使用这个选项。</li><li><strong>BUILD_TOKEN_MANAGER</strong>：boolean类型的选项，默认值是true。在默认情况下，javacc会生成一个token manager文件。如果这个选项设置成false，那么token manager文件就不会被生成了。这样做的一个场景是，你在修改语法文件的时候仅仅修改了语法，而且没有修改任何的文法规则，那么就可以通过这个选项来节约解析器的生成时间了。</li><li><strong>TOKEN_MANAGER_USES_PARSER</strong>：boolean类型的选项，默认值是false。当设置为true的时候，生成的token manager会包含一个域指向生成的解析器实例。这样做的好处是，可以在文法分析中使用解析器的一些逻辑。如果static选项设置成true了，那么这个选项将不起作用。</li><li><strong>TOKEN_EXTENDS</strong>：字符串类型的选项，默认值是“”，这意味着生成的Token对象将继承自java.lang.Object。这个选项的值可以设置成一个希望Token继承的基类。</li><li><strong>TOKEN_FACTORY</strong>：字符串类型的选项，默认值是“”，意味着Token是通过调用Token.newToken()方法生成的。通过这个选项可以指定一个Token factory，factory类需要有一个static的newToken(int ofKind, String image)方法。</li><li><strong>SANITY_CHECK</strong>：boolean类型的选项，默认值是true。在解析器生成的过程中，javacc会进行很多的语法和语义检查。诸如左递归、二义性。通过把这个选项设置成false，可以减少这些检查，从而加快生成速度。需要注意的是，即使不做这些检查，上面的现象仍然会导致解析器没有按照你想要的方式去工作。</li><li><strong>COMMON_TOKEN_ACTION</strong>：boolean类型的选项，默认值是false。如果这个选项被设置成true。那么在token被扫描进token manager后，每次调用token manager的getNextToken方法后，会触发一个对方法CommonTokenAction的访问。这个函数必须要在TOKEN_MGR_DECLS中定义。CommonTokenAction的签名如下：void CommonTokenAction(Token t)</li><li><strong>CACHE_TOKENS</strong>：boolean类型的选项，默认值是false。这个选项设置成true会导致解析器提前拿token。这样做可以提高一些性能，但是交互型的工作就能不能完成了。</li><li><strong>OUTPUT_DIRECTORY</strong>：字符串类型的选项，默认值是当前文件夹。这个选项可以控制生成的文件的输出位置。</li><li><strong>DEBUG_PARSER</strong>：boolean类型的选项，默认值是false。这个选项用于从parser中获取debug信息。为true的时候，parser会打印很多日志来显示其工作的路径。日志的跟踪也可以通过调用方法disable_tracing()方法来关闭。然后还可以通过enable_tracing()方法来打开tracing。</li><li><strong>FORCE_LA_CHECK</strong>: boolean类型的选项，默认值是false。这个选项可以控制javacc对二义性的检查。在默认情况下，二义性检查仅仅在使用LOOKAHEAD 为1的choice point进行。对于明确声明了LOOKAHEAD 或者LOOKAHEAD 的值不等于1的choice point是不做检查的。如果这个选项被设置成true，那么二义性检查将在所有的choice point处执行。</li></ol><hr><h4 id="语法分析器类"><a href="#语法分析器类" class="headerlink" title="语法分析器类"></a>语法分析器类</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">PARSER_BEGIN(parser_name)</span><br><span class="line"> . . .</span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">parser_name</span> . . . </span>&#123;</span><br><span class="line">   . . .</span><br><span class="line"> &#125;</span><br><span class="line"> . . .</span><br><span class="line"> PARSER_END(parser_name)</span><br></pre></td></tr></table></figure><p>语法分析器类的定义以标志<strong>PARSER_BEGIN</strong>和<strong>PARSER_END</strong>括起来，而且语法分析器类的名称必须与<strong>PARSER_BEGIN</strong>和<strong>PARSER_END</strong>后的名称相同。</p><blockquote><p><strong>特别注意</strong>：javaCC不会详细地检查语法文件的中java代码，java代码就是一个黑盒，所以即使通过了javaCC编译的语法文件生成的解析器还是有可能会通不过java编译器。</p></blockquote><p>生成的解析器会对语法文件中的每一个<strong>非终结符</strong>(java产生式和bnf产生式)生成一个对应的public方法。</p><h4 id="JavaCC产生式"><a href="#JavaCC产生式" class="headerlink" title="JavaCC产生式"></a>JavaCC产生式</h4><ol><li><code>javacode_production</code></li><li><code>regular_expr_production</code></li><li><code>bnf_production</code></li><li><code>token_manager_decls</code></li></ol><p><a href="https://javacc.java.net/doc/javaccgrm.html#prod9" target="_blank" rel="noopener">javacode_production</a>和<a href="https://javacc.java.net/doc/javaccgrm.html#prod11" target="_blank" rel="noopener">bnf_production</a>用来定义parser的生成规则，<a href="https://javacc.java.net/doc/javaccgrm.html#prod11" target="_blank" rel="noopener">regular_expr_production</a>用于定义Token的语法，并且javacc将依据这个信息去生成Token Manager（还会使用在Paraser语法中嵌套的Token）。<a href="https://javacc.java.net/doc/javaccgrm.html#prod12" target="_blank" rel="noopener">token_manager_decls</a>是插入到token manager中的一些申明信息。</p><hr><h4 id="javacode-production"><a href="#javacode-production" class="headerlink" title="javacode_production"></a>javacode_production</h4><p>Javacode产生式，是用java代码来书写产生式的一种手段。如果有些规则不好用EBNF来描述，并且上下文相关，就可以使用Javacode产生式，下面的例子是获取流中的一个“)”。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">JAVACODE</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">skip_to_matching_brace</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      Token tok;</span><br><span class="line">      <span class="keyword">int</span> nesting = <span class="number">1</span>;</span><br><span class="line">      <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        tok = getToken(<span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (tok.kind == LBRACE) nesting++;</span><br><span class="line">        <span class="keyword">if</span> (tok.kind == RBRACE) &#123;</span><br><span class="line">          nesting--;</span><br><span class="line">          <span class="keyword">if</span> (nesting == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        tok = getNextToken();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>上面的代码如果用在<a href="https://javacc.java.net/doc/lookahead.html" target="_blank" rel="noopener">选择点</a>的时候可能会出现问题，JavaCC中有4种选择点，分别如下:</p><ol><li>( exp1 | exp2 | … )：或</li><li>( exp )?：一次或一次都没有</li><li>( exp )*：零次或者多次</li><li>( exp )+：一次或者多次</li></ol><p>有问题的代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">NT</span><span class="params">()</span> :</span></span><br><span class="line"><span class="function">  </span>&#123;&#125;</span><br><span class="line">  &#123;</span><br><span class="line">    skip_to_matching_brace()</span><br><span class="line">  |</span><br><span class="line">    some_other_production()</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>无问题的代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">NT</span><span class="params">()</span> :</span></span><br><span class="line"><span class="function">  </span>&#123;&#125;</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"&#123;"</span> skip_to_matching_brace()</span><br><span class="line">  |</span><br><span class="line">    <span class="string">"("</span> parameter_list() <span class="string">")"</span></span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>Javacode产生式用在选择点的时候，也可以在其前面加上语义上的或者语法上的LOOKAHEAD来帮助解析器做出选择。例如:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">NT</span><span class="params">()</span> :</span></span><br><span class="line"><span class="function"> </span>&#123;&#125;</span><br><span class="line"> &#123;</span><br><span class="line">   LOOKAHEAD( &#123;errorOccurred&#125; ) skip_to_matching_brace()</span><br><span class="line"> |</span><br><span class="line">   <span class="string">"("</span> parameter_list() <span class="string">")"</span></span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>Javacode产生式的访问权限是<code>package private</code></p><hr><h4 id="bnf-production"><a href="#bnf-production" class="headerlink" title="bnf_production"></a>bnf_production</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">java_access_modifier java_return_type java_identifier &quot;(&quot; java_parameter_list &quot;)&quot; &quot;:&quot;</span><br><span class="line"></span><br><span class="line">java_block</span><br><span class="line"></span><br><span class="line">&quot;&#123;&quot; [expansion_choices](https://javacc.java.net/doc/javaccgrm.html#prod16) &quot;&#125;&quot;</span><br></pre></td></tr></table></figure><p>BNF产生式是书写javacc语法的标准产生式。每个BNF产生式的左边是一个非终结符，然后BNF产生式会在右边用BNF展开式定义这个非终结符。非终结符的写法就跟声明一个java方法的函数一样。这种写非终结符的方式非常明显，因为每个非终结符都会翻译成一个paraser类中的方法。非终结符的名字就是函数的名字，参数和返回值就是传进解析树的值和传出解析树的值。后面我们还将看见，右边的非终结符使用写起来就像一个函数调用。在解析树上传递的值跟函数的参数和返回值具有相同的范型。BNF产生式的范文权限默认是public。</p><p>一个BNF产生式的右边有<strong>两个部分</strong>。首先是一个任意的java块（包括java声明和java code）。这些代码会插入生成的方法的开头。因此，每次在解析过程中使用这个非终结符的时候，这块java代码就会被执行。任何在BNF展开式中间的java代码都可以使用这块的java代码。javacc不会对这块代码做任何处理，仅仅是收集这块的代码。因此，经过javacc处理的代码还是有可能通不过java编译器的。</p><hr><h4 id="regular-expr-production"><a href="#regular-expr-production" class="headerlink" title="regular_expr_production"></a>regular_expr_production</h4><p><strong>[ <a href="https://javacc.java.net/doc/javaccgrm.html#newprod1" target="_blank" rel="noopener">lexical_state_list</a> ]</strong></p><p><strong><a href="https://javacc.java.net/doc/javaccgrm.html#prod17" target="_blank" rel="noopener">regexpr_kind</a> [ “[“ “IGNORE_CASE” “]” ] “:”</strong></p><p><strong>“{“ <a href="https://javacc.java.net/doc/javaccgrm.html#prod18" target="_blank" rel="noopener">regexpr_spec</a> ( “|” <a href="https://javacc.java.net/doc/javaccgrm.html#prod18" target="_blank" rel="noopener">regexpr_spec</a> )* “}”</strong></p><p>正则表达式产生式用于定于被token manager处理的词法实体。关于token manager是怎么工作的可以参考：<a href="https://javacc.java.net/doc/tokenmanager.html" target="_blank" rel="noopener">this minitutorial (click here)</a>。在这里介绍的是文法实体的语法。<a href="https://javacc.java.net/doc/tokenmanager.html" target="_blank" rel="noopener">这个教程</a>会告诉你这些语法构造是怎样和token manager的实际工作联系起来的。</p><p>一个正则表达式产生式以一个文法状态开始，可以通过<a href="https://javacc.java.net/doc/javaccgrm.html#newprod1" target="_blank" rel="noopener">lexical state list</a>指定状态。token manager有一个默认的词法状态叫”DEFAULT”。如果<a href="https://javacc.java.net/doc/javaccgrm.html#newprod1" target="_blank" rel="noopener">lexical state list</a>被省略了，那么DEFAULT状态就会被使用。</p><p>状态声明的后面是一个对正则表达产生式类型的描述。<a href="https://javacc.java.net/doc/javaccgrm.html#prod17" target="_blank" rel="noopener">(see below for what this means)</a>.</p><p>然后是可选的”IGNORE_CASE”。如果出现了这个选项，那么这个正则表达式就是大小写无关的，和前面提到的<a href="https://javacc.java.net/doc/javaccgrm.html#prod6" target="_blank" rel="noopener">IGNORE_CASE</a>选项有同样的作用，区别仅仅是作用于的不同。前面提到的IGNORE_CASE的作用域是全局的。</p><p>接下来就是一些对这个正则表达式产生式的词法实体进行更详细描述的正则表达式了。</p><hr><h4 id="token-manager-decls"><a href="#token-manager-decls" class="headerlink" title="token_manager_decls"></a>token_manager_decls</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;TOKEN_MGR_DECLS&quot; &quot;:&quot; java_block</span><br></pre></td></tr></table></figure><p>Token manager的声明以”TOKEN_MGR_DECLS”保留字开始，然后是 “:” ，然后再是一些列的java声明和语句（也就是一个java block）。这些声明和语句会被写进生成的Token Manager，并且可以在<a href="https://javacc.java.net/doc/javaccgrm.html#prod18" target="_blank" rel="noopener">lexical actions</a>中访问。更多详情见 <a href="https://javacc.java.net/doc/tokenmanager.html" target="_blank" rel="noopener">the minitutorial on the token manager</a>。</p><p>在一个javacc语法文件中仅仅只能有一个Token Manager声明。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lexical_state_list::=<span class="string">"&lt;"</span> <span class="string">"*"</span> <span class="string">"&gt;"</span>|</span><br><span class="line"><span class="string">"&lt;"</span> java_identifier ( <span class="string">","</span> java_identifier )* <span class="string">"&gt;"</span></span><br></pre></td></tr></table></figure><p>文法的状态列表描述的是对应的正则表达式产生式生效的范围，可以视为一个作用域。如果使用了“&lt;&gt;”，那么这个正则表达式产生式可以在所用的状态中使用。否则对应的正则表达式产生式仅仅能够在尖括号中指定的状态中使用。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">regexpr_kind::=<span class="string">"TOKEN"</span>|</span><br><span class="line"><span class="string">"SPECIAL_TOKEN"</span>|</span><br><span class="line"><span class="string">"SKIP"</span>|</span><br><span class="line"><span class="string">"MORE"</span></span><br></pre></td></tr></table></figure><p>这个定义了正则表达式产生式的类型，包括：</p><ul><li>TOKEN: 这个产生式中的正则表达式描述了tokens的语法，主要定义语法分析阶段用到的非终结符。<br>Token Manager会根据这些正则表达式生成<a href="https://javacc.java.net/doc/apiroutines.html" target="_blank" rel="noopener">Token</a>对象并返回给parser。</li><li>SPECIAL_TOKEN: 这产生式中的正则表达式描述了特殊的Token。特殊的Token是在解析过程中没有意义的Token，也就是本BNF产生式忽略的Token。但是，这些Token还是会被传递给parser，并且parser也可以访问他们。访问特殊Token的方式是通过其相邻的Token的specialToken域。特殊Token在处理像注释这种token的时候会非常有用。可以参考这个文档以了解更多关于特殊token的知识。</li><li>SKIP: 这个产生式的规则命中的Token会被Token Manager丢弃掉。</li><li>MORE: 有时候会需要逐步地构建Token。被这种规则命中的Token会存到一个Buffer中，直到遇到下一个Token或者Special_token，然后他们和最后一个Token或者Special_token会连在一起作为一个Token返回给parser。如果一个More后面紧跟了一个SKIP，那么整个Buffer中的内容都会被丢弃掉。</li></ul><p>以上就是最核心的部分，还有很多没有讲到。下面再附带一个二元运算计算器的例子，仅供大家参考，文章有部分是借用了网友的文字，特别说明。如果想更详细的理解，请移步<a href="https://javacc.java.net/doc/javaccgrm.html" target="_blank" rel="noopener">官方文档</a>。</p><p><strong>例子：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Author: yangguo@outlook.com</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">options</span><br><span class="line">&#123;</span><br><span class="line">  LOOKAHEAD= <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  CHOICE_AMBIGUITY_CHECK = <span class="number">2</span>;</span><br><span class="line">  OTHER_AMBIGUITY_CHECK = <span class="number">1</span>;</span><br><span class="line">  STATIC = <span class="keyword">false</span>;</span><br><span class="line">  DEBUG_PARSER = <span class="keyword">false</span>;</span><br><span class="line">  DEBUG_LOOKAHEAD = <span class="keyword">false</span>;</span><br><span class="line">  DEBUG_TOKEN_MANAGER = <span class="keyword">false</span>;</span><br><span class="line">  ERROR_REPORTING = <span class="keyword">true</span>;</span><br><span class="line">  JAVA_UNICODE_ESCAPE = <span class="keyword">false</span>;</span><br><span class="line">  UNICODE_INPUT = <span class="keyword">false</span>;</span><br><span class="line">  IGNORE_CASE = <span class="keyword">false</span>;</span><br><span class="line">  USER_TOKEN_MANAGER = <span class="keyword">false</span>;</span><br><span class="line">  USER_CHAR_STREAM = <span class="keyword">false</span>;</span><br><span class="line">  BUILD_PARSER = <span class="keyword">true</span>;</span><br><span class="line">  BUILD_TOKEN_MANAGER = <span class="keyword">true</span>;</span><br><span class="line">  SANITY_CHECK = <span class="keyword">true</span>;</span><br><span class="line">  FORCE_LA_CHECK = <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">PARSER_BEGIN(Calculator)</span><br><span class="line"><span class="keyword">package</span> info.yangguo.test1.javacc;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Calculator</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String args[])</span> <span class="keyword">throws</span> ParseException</span></span><br><span class="line"><span class="function"> </span>&#123;</span><br><span class="line"> Calculator parser = <span class="keyword">new</span> Calculator(System.in);</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>)</span><br><span class="line"> &#123;</span><br><span class="line"> parser.parse();</span><br><span class="line"> &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line">PARSER_END(Calculator)</span><br><span class="line">SKIP :</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">" "</span></span><br><span class="line">| <span class="string">"\r"</span></span><br><span class="line">| <span class="string">"\t"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">TOKEN:</span><br><span class="line">&#123;</span><br><span class="line"> &lt; NUMBER: (&lt;DIGIT&gt;)+ ( <span class="string">"."</span> (&lt;DIGIT&gt;)+ )? &gt;</span><br><span class="line">| &lt; DIGIT: [<span class="string">"0"</span>-<span class="string">"9"</span>] &gt;</span><br><span class="line">| &lt; EOL: <span class="string">"\n"</span> &gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">parse</span><span class="params">()</span>:</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> <span class="keyword">double</span> result;</span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line"> result=binaryOperation() &lt;EOL&gt;</span><br><span class="line">  &#123;</span><br><span class="line">    System.out.println(result);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br><span class="line"> | &lt;EOF&gt; &#123; System.exit(-<span class="number">1</span>); &#125;</span><br><span class="line"> | &lt;EOL&gt;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**二元运算**/</span></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">binaryOperation</span><span class="params">()</span>:</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> <span class="keyword">double</span> result;</span><br><span class="line"> <span class="keyword">double</span> tmp;</span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line"> result=binaryOperationHighPriority()</span><br><span class="line"> (</span><br><span class="line"> <span class="string">"+"</span> tmp=binaryOperation() &#123; result += tmp; &#125;</span><br><span class="line"> | <span class="string">"-"</span> tmp=binaryOperation() &#123; result -= tmp; &#125;</span><br><span class="line"> )*</span><br><span class="line"> &#123; <span class="keyword">return</span> result; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**二元运算中高优先级的部分**/</span></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">binaryOperationHighPriority</span><span class="params">()</span>:</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> <span class="keyword">double</span> result;</span><br><span class="line"> <span class="keyword">double</span> tmp;</span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line"> result=unaryOperation()</span><br><span class="line"> (</span><br><span class="line"> <span class="string">"*"</span> tmp=binaryOperationHighPriority() &#123; result *= tmp; &#125;</span><br><span class="line">   | <span class="string">"/"</span> tmp=binaryOperationHighPriority() &#123; result /= tmp; &#125;</span><br><span class="line"> )*</span><br><span class="line"> &#123; <span class="keyword">return</span> result; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**一元运算**/</span></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">unaryOperation</span><span class="params">()</span>:</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> <span class="keyword">double</span> result;</span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line"> <span class="string">"-"</span> result=element() &#123; <span class="keyword">return</span> -result; &#125;</span><br><span class="line"> | result=element() &#123; <span class="keyword">return</span> result; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">double</span> <span class="title">element</span><span class="params">()</span>:</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"> Token token;</span><br><span class="line"> <span class="keyword">double</span> result;</span><br><span class="line">&#125;</span><br><span class="line">&#123;</span><br><span class="line"> token=&lt;NUMBER&gt; &#123; <span class="keyword">return</span> Double.parseDouble(token.toString()); &#125;</span><br><span class="line"> | <span class="string">"("</span> result=binaryOperation() <span class="string">")"</span> &#123; <span class="keyword">return</span> result; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近由于需要解析Eterm指令结果，对于这种不遵循标准格式（XML/JSON/HTML）的文本，我又不想大量的堆叠大量的正则表达式，因此借助Javacc来解决我的问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;编译知识&quot;&gt;&lt;a href=&quot;#编译知识&quot; class=&quot;headerlink&quot; title=&quot;编译知识&quot;&gt;&lt;/a&gt;编译知识&lt;/h3&gt;&lt;p&gt;对于码农来说，应该不需要再解释编译这回事了。一般我们将语言分为编译型语言和解释型语言，但是不管是哪种语言，都少不了&lt;strong&gt;词法分析&lt;/strong&gt;、&lt;strong&gt;语法分析&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;词法分析（lexing）&quot;&gt;&lt;a href=&quot;#词法分析（lexing）&quot; class=&quot;headerlink&quot; title=&quot;词法分析（lexing）&quot;&gt;&lt;/a&gt;词法分析（lexing）&lt;/h3&gt;&lt;p&gt;词法分析就是将文本分解成token，token就是具有特殊含义的原子单位，如果语言的保留字、标点符号、数字、字符串，当然包含空白等等，只不过有些如空白会在词法分析时忽略掉。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="Javacc" scheme="https://www.yangguo.info/tags/Javacc/"/>
    
  </entry>
  
  <entry>
    <title>Tigase集群方案及配置说明</title>
    <link href="https://www.yangguo.info/2014/09/20/tigase%E9%9B%86%E7%BE%A4%E6%96%B9%E6%A1%88%E5%8F%8A%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3/"/>
    <id>https://www.yangguo.info/2014/09/20/tigase集群方案及配置说明文档/</id>
    <published>2014-09-20T14:14:34.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>该文档主要是描述Tigase整体架构和一些配置说明，整体架构我们采用<strong>config-type=—-genconfig-def</strong>的server加<strong>config-type=—-genconfig-comp</strong>的外部component。同时还会对SM的插件和错误代码进行说明，方便大家在开发及配置时参考。由于外部component较多，此处选择了较为复杂的MUC作为配置案例。pubsub，proxy，message-archive，msn，自己开发的componet，配置都的思想都基本一样。但是限于本人认识有限，如果有任何错误或者歧义，请大家及时指正。</p></blockquote><h3 id="Server-c2s-s2s-sm-ext2s-集群-MUC-comp-集群"><a href="#Server-c2s-s2s-sm-ext2s-集群-MUC-comp-集群" class="headerlink" title="Server(c2s+s2s+sm+ext2s)集群+MUC(comp)集群"></a>Server(c2s+s2s+sm+ext2s)集群+MUC(comp)集群</h3><h4 id="架构图"><a href="#架构图" class="headerlink" title="架构图"></a>架构图</h4><p><img src="/images/ExternalCompClustering.003_0.png" alt="tigase架构图"></p><a id="more"></a><h4 id="Server1-hostname-vm-128-157-配置"><a href="#Server1-hostname-vm-128-157-配置" class="headerlink" title="Server1(hostname: vm-128-157)配置"></a>Server1(hostname: vm-128-157)配置</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">--debug = server,cluster,xmpp.impl</span><br><span class="line"> --virt-hosts = xmpp.dev.pajkdc.com</span><br><span class="line">--user-db-uri = jdbc:mysql://10.0.128.115:3307/tigase?user=pajk&amp;password=qawsed123</span><br><span class="line">--user-db = mysql</span><br><span class="line">--admins = admin@xmpp.dev.pajkdc.com</span><br><span class="line">config-type = --gen-config-def</span><br><span class="line">--cluster-mode = true</span><br><span class="line">--cluster-connect-all = true</span><br><span class="line">--cluster-nodes=vm-128-157:yangguo:5277,vm-128-158:yangguo:5277</span><br><span class="line">--sm-plugins =-starttls,+urn:ietf:params:xml:ns:messageConsumeACK,+urn:ietf:params:xml:ns:xmpp-user,-message-archive-xep-0136,-jabber:iq:register,-presence,-http://jabber.org/protocol/stats,-vcard-temp,-jabber:iq:roster,-pep</span><br><span class="line">--comp-name-1 = ext</span><br><span class="line">--comp-class-1 = tigase.server.ext.ComponentProtocol</span><br><span class="line">--external = muc.xmpp.dev.pajkdc.com:yangguo:listen:5270:vm-128-157:accept:ReceiverBareJidLB</span><br><span class="line">--auth-domain-repo-pool=com.pajk.im.repository.ExtraAuthRepositoryMDImpl</span><br><span class="line">--user-domain-repo-pool=com.pajk.im.repository.ExtraUserRepositoryMDImpl</span><br><span class="line">--comp-name-2 = ChannelMsgExtComponent</span><br><span class="line">--comp-class-2 = com.pajk.im.cmp.ChannelMsgExtComponent</span><br><span class="line">--cm-ht-traffic-throttling = xmpp:250k:0:disc,bin:1024m:0:disc</span><br><span class="line">--cm-traffic-throttling=xmpp:2500:0:disc,bin:10m:0:disc</span><br><span class="line">--new-connections-throttling=5222:500,5223:50,5269:100,5280:1000,5290:100</span><br><span class="line">--net-buff-high-throughput=256k</span><br></pre></td></tr></table></figure><hr><h4 id="Server2-hostname-vm-128-158-配置"><a href="#Server2-hostname-vm-128-158-配置" class="headerlink" title="Server2(hostname: vm-128-158)配置"></a>Server2(hostname: vm-128-158)配置</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">--debug = server,cluster,xmpp.impl</span><br><span class="line">--virt-hosts = xmpp.dev.pajkdc.com</span><br><span class="line">--user-db-uri = jdbc:mysql://10.0.128.115:3307/tigase?user=pajk&amp;password=qawsed123</span><br><span class="line">--user-db = mysql</span><br><span class="line">--admins = admin@xmpp.dev.pajkdc.com</span><br><span class="line">config-type = --gen-config-def</span><br><span class="line">--cluster-mode = true</span><br><span class="line">--cluster-connect-all = true</span><br><span class="line">--cluster-nodes=vm-128-157:yangguo:5277,vm-128-158:yangguo:5277</span><br><span class="line">--sm-plugins =-starttls,+urn:ietf:params:xml:ns:messageConsumeACK,+urn:ietf:params:xml:ns:xmpp-user,-message-archive-xep-0136,-jabber:iq:register,-presence,-http://jabber.org/protocol/stats,-vcard-temp,-jabber:iq:roster,-pep</span><br><span class="line">--comp-name-1 = ext</span><br><span class="line">--comp-class-1 = tigase.server.ext.ComponentProtocol</span><br><span class="line">--external = muc.xmpp.dev.pajkdc.com:yangguo:listen:5270:vm-128-158:accept:ReceiverBareJidLB</span><br><span class="line">--auth-domain-repo-pool=com.pajk.im.repository.ExtraAuthRepositoryMDImpl</span><br><span class="line">--user-domain-repo-pool=com.pajk.im.repository.ExtraUserRepositoryMDImpl</span><br><span class="line">--comp-name-2 = ChannelMsgExtComponent</span><br><span class="line">--comp-class-2 = com.pajk.im.cmp.ChannelMsgExtComponent</span><br><span class="line">--cm-ht-traffic-throttling = xmpp:250k:0:disc,bin:1024m:0:disc</span><br><span class="line">--cm-traffic-throttling=xmpp:2500:0:disc,bin:10m:0:disc</span><br><span class="line">--new-connections-throttling=5222:500,5223:50,5269:100,5280:1000,5290:100</span><br><span class="line">--net-buff-high-throughput=256k</span><br></pre></td></tr></table></figure><hr><h4 id="muc1配置"><a href="#muc1配置" class="headerlink" title="muc1配置"></a>muc1配置</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">config-type = --gen-config-comp</span><br><span class="line">--user-db = mysql</span><br><span class="line">--admins = admin@xmpp.dev.pajkdc.com</span><br><span class="line">--user-db-uri = jdbc:mysql://10.0.128.115:3307/tigase?user=pajk&amp;password=qawsed123</span><br><span class="line">--virt-hosts = xmpp.dev.pajkdc.com</span><br><span class="line">--comp-name-1 = muc</span><br><span class="line">--debug = server</span><br><span class="line">--comp-class-1 = tigase.muc.MUCComponent</span><br><span class="line">--external = muc.xmpp.dev.pajkdc.com:yangguo:connect:5270:xmpp.dev.pajkdc.com;vm-128-157;vm-128-158:accept</span><br></pre></td></tr></table></figure><hr><h4 id="muc2配置"><a href="#muc2配置" class="headerlink" title="muc2配置"></a>muc2配置</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">config-type = --gen-config-comp</span><br><span class="line">--user-db = mysql</span><br><span class="line">--admins = admin@xmpp.dev.pajkdc.com</span><br><span class="line">--user-db-uri = jdbc:mysql://10.0.128.115:3307/tigase?user=pajk&amp;password=qawsed123</span><br><span class="line">--virt-hosts = xmpp.dev.pajkdc.com</span><br><span class="line">--comp-name-1 = muc</span><br><span class="line">--debug = server</span><br><span class="line">--comp-class-1 = tigase.muc.MUCComponent</span><br><span class="line">--external = muc.xmpp.dev.pajkdc.com:yangguo:connect:5270:xmpp.dev.pajkdc.com;vm-128-157;vm-128-158:accept</span><br></pre></td></tr></table></figure><hr><h3 id="Tigase-MUC思路-引用于Tigase作者Kobit"><a href="#Tigase-MUC思路-引用于Tigase作者Kobit" class="headerlink" title="Tigase MUC思路(引用于Tigase作者Kobit)"></a>Tigase MUC思路(引用于Tigase作者Kobit)</h3><blockquote><p>As far as I know there is no such thing like a persistent member of the MUC room. This is not a part of the protocol hence it is not implemented anywhere. Even if we had created something like that in our MUC server it would not work with any XMPP client.<br>The whole idea behind MUC is to allow for communication between ONLINE users. Therefore, once the user logs off it is removed from the MUC.<br>However, most of the XMPP clients have a feature to save a MUC bookmark and automatically join the room after login to the XMPP Server.<br>The MUC room has quite extensive configuration settings, you can list users who are allowed to join the room, you can make the room password protected, you can assign different roles to different users. All of this is supported by our MUC.</p></blockquote><hr><p><code>注意事项</code>:上面的配置，由于是一个完整的分布式环境，所以特别需要注意网络状况。特别是在server端开启本地端口，监听外部component服务链接的时候。尤其是使用无线网络的笔记本电脑，有时候无线网络会导致里面的java代码不能绑定本地端口的情况。面对这种情况，请大家将无线网络断开重连。</p><hr><h3 id="Tigase-XMPP-Server-configuration-properties"><a href="#Tigase-XMPP-Server-configuration-properties" class="headerlink" title="Tigase XMPP Server configuration properties"></a>Tigase XMPP Server configuration properties</h3><table><thead><tr><th>参数</th><th>说明</th><th>参考</th></tr></thead><tbody><tr><td>–admins</td><td>管理员账号,管理可以disco插件对server进行管理</td><td></td></tr><tr><td>–auth-db</td><td>权限认证的db,支持mysql/pgsql/ldap/drupal/tigase-auth/tigase-custom/class name</td><td></td></tr><tr><td>–auth-db-uri</td><td>权限认证db的uri</td><td></td></tr><tr><td>–auth-domain-repo-pool</td><td></td><td></td></tr><tr><td>–auth-repo-pool</td><td></td><td></td></tr><tr><td>–auth-repo-pool-size</td><td></td><td></td></tr><tr><td>–bind-ext-hostnames</td><td></td><td></td></tr><tr><td>–bosh-close-connection</td><td></td><td></td></tr><tr><td>–bosh-extra-headers-file</td><td></td><td></td></tr><tr><td>–cl-conn-repo-class</td><td></td><td></td></tr><tr><td>–client-access-policy-file</td><td></td><td></td></tr><tr><td>–cluster-connect-all</td><td>动态的将不在集群配置中的节点加入到集群中，默认为false</td><td></td></tr><tr><td>–cluster-mode</td><td>是否开启集群</td><td></td></tr><tr><td>–cluster-nodes</td><td>在集群每台机器上确保每个hostname能够正常被DNS解析</td><td><a href="http://www.tigase.org/content/cluster-nodes" target="_blank" rel="noopener">参考</a></td></tr><tr><td>–cm-ht-traffic-throttling</td><td>s2s,external component流控</td><td></td></tr><tr><td>–cm-see-other-host</td><td>xmpp提供的load balance方案</td><td><a href="http://wiki.jabbercn.org/RFC6120#see-other-host" target="_blank" rel="noopener">参考1</a>,<a href="http://www.tigase.org/content/tigase-load-balancing" target="_blank" rel="noopener">参考2</a>,<a href="http://www.tigase.org/content/xep-0280-message-carbons" target="_blank" rel="noopener">参考3</a></td></tr><tr><td>–cm-traffic-throttling</td><td>c2s的流控，每分钟xmpp包为2500个，整个生命周期没有限制;每分钟的流量为20m，整个生命周期没有限制</td><td></td></tr><tr><td>–cmpname-ports</td><td></td><td></td></tr><tr><td>–comp-class</td><td>需要加载到server的额外的component，特别是使用了config-type分离了各个component的应用中</td><td><a href="http://www.tigase.org/content/comp-class" target="_blank" rel="noopener">参考</a></td></tr><tr><td>–comp-name</td><td>非独立的component(需要被server加载)名称</td><td><a href="http://www.tigase.org/content/comp-name" target="_blank" rel="noopener">参考</a></td></tr><tr><td>–cross-domain-policy-file</td><td></td><td></td></tr><tr><td>–data-repo-pool-size</td><td></td><td></td></tr><tr><td>–debug</td><td>开启调试模式</td><td><a href="http://www.tigase.org/tigase-debuging" target="_blank" rel="noopener">参考</a></td></tr><tr><td>–debug-packages</td><td>输出指定包路径的日志，多个包用逗号分隔</td><td></td></tr><tr><td>–domain-filter-policy</td><td></td><td></td></tr><tr><td>–elements-number-limit</td><td></td><td></td></tr><tr><td>–ext-comp</td><td>弃用，建议使用–external来加载外部component(如:MUC/PubSub/IM transort)，内部component还只能使用这个，如CS和SM分离</td><td><a href="http://www.tigase.org/content/ext-comp" target="_blank" rel="noopener">参考</a></td></tr><tr><td>–extcomp-repo-class</td><td></td><td></td></tr><tr><td>–external</td><td>监听外部component或者将component连接到server</td><td><a href="http://www.tigase.org/content/external" target="_blank" rel="noopener">参考1</a>，<a href="http://www.tigase.org/content/basic-configuration-options-external-component" target="_blank" rel="noopener">参考2</a></td></tr><tr><td>–hardened-mode</td><td></td><td></td></tr><tr><td>–max-queue-size</td><td></td><td></td></tr><tr><td>–monitoring</td><td></td><td></td></tr><tr><td>–net-buff-high-throughput</td><td>默认为64k，服务器之间通信如果流量较大，可以适度调大该值，线上调为256k</td><td></td></tr><tr><td>–net-buff-standard</td><td></td><td></td></tr><tr><td>–new-connections-throttling</td><td>各个端口每秒新建连接的流控，主要是防止机器重启时，大量连接打死server</td><td></td></tr><tr><td>–nonpriority-queue</td><td></td><td></td></tr><tr><td>–queue-implementation</td><td></td><td></td></tr><tr><td>–roster-implementation</td><td></td><td></td></tr><tr><td>–s2s-ejabberd-bug-workaround-active</td><td></td><td></td></tr><tr><td>–s2s-secret</td><td></td><td></td></tr><tr><td>–s2s-skip-tls-hostnames</td><td></td><td></td></tr><tr><td>–script-dir</td><td></td><td></td></tr><tr><td>–sm-cluster-strategy-class</td><td></td><td></td></tr><tr><td>–sm-plugins</td><td>很多无用的plugin建议不加载，特别是正式环境中</td><td></td></tr><tr><td>–sm-threads-pool</td><td></td><td></td></tr><tr><td>–ssl-certs-location</td><td></td><td></td></tr><tr><td>–ssl-container-class</td><td></td><td></td></tr><tr><td>–ssl-def-cert-domain</td><td></td><td></td></tr><tr><td>–stats-archiv</td><td></td><td></td></tr><tr><td>–stats-history</td><td></td><td></td></tr><tr><td>–stringprep-processor</td><td></td><td></td></tr><tr><td>–test</td><td></td><td></td></tr><tr><td>–tigase-config-repo-class</td><td></td><td></td></tr><tr><td>–tigase-config-repo-uri</td><td></td><td></td></tr><tr><td>–tls-jdk-nss-bug-workaround-active</td><td></td><td></td></tr><tr><td>–trusted</td><td></td><td></td></tr><tr><td>–user-db</td><td></td><td></td></tr><tr><td>–user-db-uri</td><td></td><td></td></tr><tr><td>–user-domain-repo-pool</td><td></td><td></td></tr><tr><td>–user-repo-pool</td><td></td><td></td></tr><tr><td>–user-repo-pool-size</td><td></td><td></td></tr><tr><td>–vhost-anonymous-enabled</td><td></td><td></td></tr><tr><td>–vhost-max-users</td><td></td><td></td></tr><tr><td>–vhost-message-forward-jid</td><td></td><td></td></tr><tr><td>–vhost-presence-forward-jid</td><td></td><td></td></tr><tr><td>–vhost-register-enabled</td><td></td><td></td></tr><tr><td>–vhost-tls-required</td><td></td><td></td></tr><tr><td>–virt-hosts</td><td>虚拟域设置</td><td></td></tr><tr><td>–watchdog_delay</td><td>心跳检测间隔时间，写检测，默认值10分钟，现在修改为30秒</td><td></td></tr><tr><td>–watchdog_ping_type</td><td>watchdog ping包类型，写操作，whitespace和xmpp</td><td></td></tr><tr><td>–watchdog_timeout</td><td>读检测，默认1740000毫秒</td><td></td></tr><tr><td>config-type</td><td>选择启动的方式，CM和SM分离就是此处配置</td><td><a href="http://www.tigase.org/content/config-type" target="_blank" rel="noopener">参考</a></td></tr></tbody></table><hr><h3 id="sm-plugin说明"><a href="#sm-plugin说明" class="headerlink" title="sm-plugin说明"></a>sm-plugin说明</h3><table><thead><tr><th>参数</th><th>说明</th><th>参考</th></tr></thead><tbody><tr><td>jabber:iq:register</td><td>注册服务</td><td></td></tr><tr><td>message-archive-xep-0136</td><td>消息归档</td><td></td></tr><tr><td>jabber:iq:auth</td><td>简单用户认证</td><td></td></tr><tr><td>urn:ietf:params:xml:ns:xmpp-sasl</td><td>SASL协商</td><td><a href="http://wiki.jabbercn.org/RFC6120#SASL.E5.8D.8F.E5.95.86" target="_blank" rel="noopener">参考</a></td></tr><tr><td>urn:ietf:params:xml:ns:xmpp-bind</td><td>资源绑定</td><td></td></tr><tr><td>urn:ietf:params:xml:ns:xmpp-session</td><td>session绑定</td><td></td></tr><tr><td>jabber:iq:roster</td><td>联系人名单管理</td><td></td></tr><tr><td>presence</td><td>xmpp顶级元素，上线广播</td><td></td></tr><tr><td>jabber:iq:privacy</td><td>隐身协议</td><td></td></tr><tr><td>jabber:iq:version</td><td>客户端版本</td><td></td></tr><tr><td><a href="http://jabber.org/protocol/stats" target="_blank" rel="noopener">http://jabber.org/protocol/stats</a></td><td>是否发送统计信息，指向jabber.org发送</td><td></td></tr><tr><td>startls</td><td>tls加密</td><td></td></tr><tr><td>msgoffline</td><td>离线消息</td><td></td></tr><tr><td>vcard-temp</td><td>临时的vCard</td><td></td></tr><tr><td><a href="http://jabber.org/protocol/commands" target="_blank" rel="noopener">http://jabber.org/protocol/commands</a></td><td>管理virtual domains的特别命令</td><td><a href="http://www.tigase.org/content/specification-ad-hoc-commands-used-manage-virtual-domains" target="_blank" rel="noopener">参考</a></td></tr><tr><td>jabber:iq:private</td><td>私有数据存储</td><td></td></tr><tr><td>urn:xmpp:ping</td><td>心跳检测</td><td></td></tr><tr><td>pep</td><td>发布订阅插件</td><td><a href="http://www.tigase.org/content/extending-pep-plugin" target="_blank" rel="noopener">参考</a></td></tr><tr><td>domain-filter(basic-filter)</td><td>domain拦截器</td><td><a href="http://www.tigase.org/content/domain-filter-policy" target="_blank" rel="noopener">参考</a></td></tr><tr><td>amp(basic-filter)</td><td>高级消息处理</td><td><a href="http://wiki.jabbercn.org/XEP-0079" target="_blank" rel="noopener">参考1</a>，<a href="http://www.tigase.org/content/advanced-message-processing-amp-xep-0079" target="_blank" rel="noopener">参考2</a></td></tr><tr><td>zlib(basic-filter)</td><td>zlib压缩</td><td></td></tr><tr><td>message-carbons(basic-filter)</td><td>将stanzas投递到用户指定的资源</td><td></td></tr><tr><td>disco(basic-filter)</td><td>服务发现</td><td></td></tr></tbody></table><hr><h3 id="标准错误代码"><a href="#标准错误代码" class="headerlink" title="标准错误代码"></a>标准错误代码</h3><table><thead><tr><th>代码</th><th>说明</th></tr></thead><tbody><tr><td>302</td><td>重定向，尽管HTTP规定中包含八种不同代码来表示重定向，Jabber只用了其中一个（用来代替所有的重定向错误）。不过Jabber代码302是为以后的功能预留的，目前还没有用到。</td></tr><tr><td>400</td><td>坏请求，Jabber代码400用来通知Jabber客户端，一个请求因为其糟糕的语法不能被识别。例如，当一个Jabber客户端发送一个的订阅请求给它自己活发送一条没有包含“to”属性的消息，Jabber代码400就会产生。</td></tr><tr><td>401</td><td>未授权的，Jabber代码401用来通知Jabber客户端它们提供的是错误的认证信息，如，在登陆一个Jabber服务器时使用一个错误的密码，或未知的用户名。</td></tr><tr><td>402</td><td>所需的费用，Jabber代码402为未来使用进行保留，目前还不用到。</td></tr><tr><td>403</td><td>禁止，Jabber代码403被Jabber服务器用来通知Jabber客户端该客户端的请求可以识别，但服务器拒绝执行。目前只用在注册过程中的密码存储失败。</td></tr><tr><td>404</td><td>没有找到，Jabber代码404用来表明Jabber服务器找不到任何与JabberID匹配的内容，该JabberID是一个Jabber客户端发送消息的目的地。如，一个用户打算向一个不存在的JabberID发送一条消息。如果接受者的Jabber服务器无法到达，将发送一个来自500级数的错误代码。</td></tr><tr><td>405</td><td>不允许的，Jabber代码405用在不允许操作被’from’地址标识的JabberID。例如，它可能产生在，一个非管理员用户试图在服务器上发送一条管理员级别的消息，或者一个用户试图发送一台Jabber服务器的时间或版本，或者发送一个不同的JabberID的vCard。</td></tr><tr><td>406</td><td>不被接受的，Jabber代码406用于服务器因为某些理由不接受一个包。例如，这个可能发生在，一个Jabber客户端试图使用jabber:iq:private在服务器上存储信息，但当前的用于存储的名字空间用”jabber:”开头（在Jabber里是一个被存的XML开头）。另一种可能产生406错误的情况是当一个Jabber客户端试图用一个空密码注册到一台Jabber服务器上。</td></tr><tr><td>407</td><td>必须注册，Jabber代码407当前不被使用</td></tr><tr><td>408</td><td>注册超时，当一个Jabber客户端不能在服务器准备好的时间内发起一个请求时，Jabber服务器生成Jabber代码408。这个代码当前只用于Jabber会话管理器使用的零度认证模式中。</td></tr><tr><td>409</td><td><a href="http://wiki.jabbercn.org/RFC6120#.E8.B5.84.E6.BA.90.E7.BB.91.E5.AE.9A" target="_blank" rel="noopener">冲突</a></td></tr><tr><td>500</td><td>服务器内部错误，当一台Jabber服务器遇到一种预期外的条件，该条件阻止服务器处理来自Jabber客户端的包，这是将用到Jabber代码500。现在，唯一会引发500错误代码的时间是当一个Jabber客户端试图通过服务器认证，而该认证因为某些原因没有被处理（如无法保存密码）。</td></tr><tr><td>501</td><td>不可执行，当服务器不支持Jabber客户端请求的功能，使用Jabber代码501。例如，该代码只当Jabber客户端发送一个认证请求，而该认证请求不包含服务器配置中定义的任何一种认证方式时，服务器发送Jabber代码501。这个代码还被用于，当一个Jabber客户端试图注册一个不允许注册的服务器。</td></tr><tr><td>502</td><td>远程服务器错误，当因为无法到达远程服务器导致转发一个包失败时，使用Jabber代码502。该代码发送的特殊例子包括一个远程服务器的连接的失败，无法获取远程服务器的主机名，以及远程服务器错误导致的外部时间过期。</td></tr><tr><td>503</td><td>服务无法获得，当一个Jabber客户端请求一个服务，而Jabber服务器通常由于一些临时原因无法提供该服务时，使用Jabber代码503。例如，一个Jabber客户端试图发送一条消息给另一个用户，该用户不在线，但它的服务器不提供离线存储服务，服务器将返回一个503错误代码给发送消息的JabberID。当为vcard-temp和jabber:iq:private名字空间设置信息时，出现通过xdb进行数据存储的写入错误，也使用该代码。</td></tr><tr><td>504</td><td>远程服务器超时，Jabber代码504用于下列情况:试图连接一台服务器发生超时，错误的服务器名。</td></tr><tr><td>510</td><td>连接失败，Jabber代码510目前还没有使用。</td></tr></tbody></table><blockquote><p>扩展code(XMPPErrorCodeExtension枚举)，如果大家定义了，请加在此处。<br>ERROR_TH(4031, “cancel”, “登陆过于频繁或者流量过大”)</p></blockquote><hr><h3 id="MUC相关技术"><a href="#MUC相关技术" class="headerlink" title="MUC相关技术"></a>MUC相关技术</h3><ul><li><a href="http://wiki.jabbercn.org/XEP-0004" target="_blank" rel="noopener">XEP-0004</a>: 数据表单，用来交换数据。</li></ul><hr><h3 id="CS和SM分离"><a href="#CS和SM分离" class="headerlink" title="CS和SM分离"></a>CS和SM分离</h3><ul><li>tigase中是可行的，但是目前架构没有这样做。这样做会增加网络开销，目前此方案，不是很好，待尝试。</li></ul><hr><h3 id="使用到得XEP"><a href="#使用到得XEP" class="headerlink" title="使用到得XEP"></a>使用到得XEP</h3><ul><li><a href="http://www.xmpp.org/extensions/xep-0184.html" target="_blank" rel="noopener">XEP-0184</a>: Message Delivery  Receipts，该扩展对server没有任何要求，只要client端支持就行。</li><li><a href="http://wiki.jabbercn.org/XEP-0004" target="_blank" rel="noopener">XEP-0004</a>:表单数据，用来交换数据，form类型[<a href="http://xmpp.org/registrar/formtypes.html]" target="_blank" rel="noopener">http://xmpp.org/registrar/formtypes.html]</a></li><li><a href="http://xmpp.org/extensions/xep-0198.html" target="_blank" rel="noopener">XEP-0198</a>:server端的消息确认，<a href="http://www.tigase.org/content/stream-management" target="_blank" rel="noopener">tigase中的配置</a>，它的存在的意义在<a href="http://op-co.de/blog/posts/XEP-0198" target="_blank" rel="noopener">http://op-co.de/blog/posts/XEP-0198</a>中已经描述的非常详细。</li><li>XEP-199</li><li><a href="http://wiki.jabbercn.org/XEP-0114" target="_blank" rel="noopener">XEP-0114</a>:Jabber组件协议</li></ul><hr><h3 id="群聊室的属性"><a href="#群聊室的属性" class="headerlink" title="群聊室的属性"></a>群聊室的属性</h3><blockquote><p>我们设计的群已经不是标准的xmpp群了，下面的属性是对于smack或者标准的xmpp群有意义的。</p></blockquote><table><thead><tr><th>说明</th><th>配置项</th></tr></thead><tbody><tr><td> 房间名称</td><td>muc#roomconfig_roomname</td></tr><tr><td> 描述</td><td>muc#roomconfig_roomdesc</td></tr><tr><td> 允许占有者更改主题</td><td>muc#roomconfig_changesubject</td></tr><tr><td> 最大房间占有者人数</td><td>muc#roomconfig_maxusers</td></tr><tr><td> 其 Presence 是 Broadcast 的角色</td><td>muc#roomconfig_presencebroadcast</td></tr><tr><td> 列出目录中的房间</td><td>muc#roomconfig_publicroom</td></tr><tr><td> 房间是持久的</td><td>muc#roomconfig_persistentroom</td></tr><tr><td> 房间是适度的</td><td>muc#roomconfig_moderatedroom</td></tr><tr><td> 房间仅对成员开放</td><td>muc#roomconfig_membersonly</td></tr><tr><td> 允许占有者邀请其他人</td><td>muc#roomconfig_allowinvites</td></tr><tr><td> 需要密码才能进入房间</td><td>muc#roomconfig_passwordprotectedroom</td></tr><tr><td> 密码</td><td>muc#roomconfig_roomsecret</td></tr><tr><td> 能够发现占有者真实 JID 的角色</td><td>muc#roomconfig_whois</td></tr><tr><td> 登录房间对话</td><td>muc#roomconfig_enablelogging</td></tr><tr><td> 仅允许注册的昵称登录</td><td>x-muc#roomconfig_reservednick</td></tr><tr><td> 允许使用者修改昵称</td><td>x-muc#roomconfig_canchangenick</td></tr><tr><td> 允许用户注册房间</td><td>x-muc#roomconfig_registration</td></tr><tr><td> 房间管理员</td><td>muc#roomconfig_roomadmins</td></tr><tr><td> 房间拥有者</td><td>muc#roomconfig_roomowners</td></tr></tbody></table><hr><h3 id="线上服务器环境配置"><a href="#线上服务器环境配置" class="headerlink" title="线上服务器环境配置"></a>线上服务器环境配置</h3><blockquote><p>tcp调优参考我之前的文章<a href="/2013/10/30/TCP参数调整">Linux TCP参数调整</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;该文档主要是描述Tigase整体架构和一些配置说明，整体架构我们采用&lt;strong&gt;config-type=—-genconfig-def&lt;/strong&gt;的server加&lt;strong&gt;config-type=—-genconfig-comp&lt;/strong&gt;的外部component。同时还会对SM的插件和错误代码进行说明，方便大家在开发及配置时参考。由于外部component较多，此处选择了较为复杂的MUC作为配置案例。pubsub，proxy，message-archive，msn，自己开发的componet，配置都的思想都基本一样。但是限于本人认识有限，如果有任何错误或者歧义，请大家及时指正。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;Server-c2s-s2s-sm-ext2s-集群-MUC-comp-集群&quot;&gt;&lt;a href=&quot;#Server-c2s-s2s-sm-ext2s-集群-MUC-comp-集群&quot; class=&quot;headerlink&quot; title=&quot;Server(c2s+s2s+sm+ext2s)集群+MUC(comp)集群&quot;&gt;&lt;/a&gt;Server(c2s+s2s+sm+ext2s)集群+MUC(comp)集群&lt;/h3&gt;&lt;h4 id=&quot;架构图&quot;&gt;&lt;a href=&quot;#架构图&quot; class=&quot;headerlink&quot; title=&quot;架构图&quot;&gt;&lt;/a&gt;架构图&lt;/h4&gt;&lt;p&gt;&lt;img src=&quot;/images/ExternalCompClustering.003_0.png&quot; alt=&quot;tigase架构图&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="IM" scheme="https://www.yangguo.info/tags/IM/"/>
    
      <category term="Tigase" scheme="https://www.yangguo.info/tags/Tigase/"/>
    
  </entry>
  
  <entry>
    <title>Tigase Watchdog分析及优化</title>
    <link href="https://www.yangguo.info/2014/09/10/Tigase_Watchdog%E5%88%86%E6%9E%90%E5%8F%8A%E4%BC%98%E5%8C%96/"/>
    <id>https://www.yangguo.info/2014/09/10/Tigase_Watchdog分析及优化/</id>
    <published>2014-09-10T15:14:34.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>TCP链接回收</strong>是所有长连接服务器必须要面对的问题，我前面的有些文章其实已经涉及到该方面的知识。但是本篇文章主要分析和讲述tigase在移动网络中，链接的回收原理及优化方案。</p></blockquote><h3 id="心跳"><a href="#心跳" class="headerlink" title="心跳"></a>心跳</h3><p>心跳的作用是告诉对方，自己还活着；当然有人说还可以判断链路是否可到达，但是我觉得在JAVA层面这个不是很靠谱。因为当数据刷新到TCP的缓冲区之后，就成功返回了。这也正是即使使用TCP，还需要在业务层封装各种ACK包的原因，即使TCP是可靠链接，它的可靠我理解为是传输时可靠，而非传输前往buffer中写数据的可靠。</p><p>在开始之前，首先明确两个概念。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1. S2S:服务器与服务器之间的通信，网络稳定，基本在同一个IDC内部，当然也有可能跨IDC。</span><br><span class="line">2. C2S:移动设备与服务器之间的通信，网络不稳定。</span><br></pre></td></tr></table></figure><a id="more"></a><hr><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>同事在测试时发现kill -9或者直接将测试机断网之后，发现大量的established connections没有回收，也就是说session的回收出现问题。</p><h3 id="定位"><a href="#定位" class="headerlink" title="定位"></a>定位</h3><p>第一反应，问题肯定是出现在<strong>Watchdog</strong>了，直接上代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Looks in all established connections and checks whether any of them is</span></span><br><span class="line"><span class="comment">     * dead....</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">Watchdog</span></span></span><br><span class="line"><span class="class">            <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Method description</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// Sleep...</span></span><br><span class="line">                    <span class="comment">//----------作者的原代码</span></span><br><span class="line">                    <span class="comment">//Thread.sleep(10 * MINUTE);</span></span><br><span class="line">                    <span class="comment">//----------作者的原代码</span></span><br><span class="line">                    <span class="comment">//----------修改后的代码</span></span><br><span class="line">                    <span class="comment">//10分钟时间太长了，对于移动网络来说</span></span><br><span class="line">                    Thread.sleep(<span class="number">30</span> * SECOND);</span><br><span class="line">                    <span class="comment">//----------修改后的代码</span></span><br><span class="line">                    ++watchdogRuns;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// Walk through all connections and check whether they are</span></span><br><span class="line">                    <span class="comment">// really alive...., try to send space for each service which</span></span><br><span class="line">                    <span class="comment">// is inactive for hour or more and close the service</span></span><br><span class="line">                    <span class="comment">// on Exception</span></span><br><span class="line">                    doForAllServices(<span class="keyword">new</span> ServiceChecker&lt;IO&gt;() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">check</span><span class="params">(<span class="keyword">final</span> XMPPIOService service)</span> </span>&#123;</span><br><span class="line">                            <span class="keyword">try</span> &#123;</span><br><span class="line">                                <span class="keyword">if</span> (<span class="keyword">null</span> != service) &#123;</span><br><span class="line">                                    <span class="keyword">long</span> curr_time = System.currentTimeMillis();</span><br><span class="line">                                    <span class="keyword">long</span> lastTransfer = service.getLastTransferTime();</span><br><span class="line"></span><br><span class="line">                                    <span class="keyword">if</span> (curr_time - lastTransfer &gt;= maxInactivityTime) &#123;</span><br><span class="line"></span><br><span class="line">                                        <span class="comment">// Stop the service is max keep-alive time is exceeded</span></span><br><span class="line">                                        <span class="comment">// for non-active connections.</span></span><br><span class="line">                                        <span class="keyword">if</span> (log.isLoggable(Level.INFO)) &#123;</span><br><span class="line">                                            log.log(Level.INFO,</span><br><span class="line">                                                    <span class="string">"&#123;0&#125;: Max inactive time exceeded, stopping: &#123;1&#125;"</span>,</span><br><span class="line">                                                    <span class="keyword">new</span> Object[]&#123;getName(),</span><br><span class="line">                                                            service&#125;);</span><br><span class="line">                                        &#125;</span><br><span class="line">                                        ++watchdogStopped;</span><br><span class="line">                                        service.stop();</span><br><span class="line">                                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                        <span class="comment">//此处的判断对于c2s来说是无意义的，此处写超时不修改的原因是，ClientConnectionManager中的时间已经修改。对于S2S,BOSH,CLUSTER写是有必要的                                    </span></span><br><span class="line">                                        <span class="keyword">if</span> (curr_time - lastTransfer &gt;= (<span class="number">29</span> * MINUTE)) &#123;</span><br><span class="line"></span><br><span class="line">                                            <span class="comment">// At least once an hour check if the connection is</span></span><br><span class="line">                                            <span class="comment">// still alive.</span></span><br><span class="line">                                            service.writeRawData(<span class="string">" "</span>);</span><br><span class="line">                                            ++watchdogTests;</span><br><span class="line">                                        &#125;</span><br><span class="line">                                    &#125;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"></span><br><span class="line">                                <span class="comment">// Close the service....</span></span><br><span class="line">                                <span class="keyword">try</span> &#123;</span><br><span class="line">                                    <span class="keyword">if</span> (service != <span class="keyword">null</span>) &#123;</span><br><span class="line">                                        log.info(getName() + <span class="string">"Found dead connection, stopping: "</span> + service);</span><br><span class="line">                                        ++watchdogStopped;</span><br><span class="line">                                        service.forceStop();</span><br><span class="line">                                    &#125;</span><br><span class="line">                                &#125; <span class="keyword">catch</span> (Exception ignore) &#123;</span><br><span class="line"></span><br><span class="line">                                    <span class="comment">// Do nothing here as we expect Exception to be thrown here...</span></span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;    <span class="comment">/* Do nothing here */</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;    <span class="comment">// ConnectionManager</span></span><br></pre></td></tr></table></figure><p>注释我已经写的很清楚，对于不稳定的移动网络来说，10分钟的时间的确太长了。作者的开发当时主要是针对有线网络而设计的，所以此处需要优化。然后注意这行：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (curr_time - lastTransfer &gt;= maxInactivityTime) &#123;</span><br></pre></td></tr></table></figure><p>马上又能定位到<strong>ClientConnectionManager</strong>的下面方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">protected</span> <span class="keyword">long</span> <span class="title">getMaxInactiveTime</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">3</span> * MINUTE;</span><br><span class="line">      <span class="comment">//-------作者原代码</span></span><br><span class="line"><span class="comment">//return 24 * HOUR;</span></span><br><span class="line"><span class="comment">//-------作者原代码</span></span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>此处我修改为了3分钟，因为我们手机端心跳周期是59(质数)秒，我们默认3个周期如果没有心跳上来，就表示链接坏掉了。当然这个地方我为什么只改<strong>ClientConnectionManager</strong>，而不改动Bosh、S2S、WebSocket等，因为这些我们都认为是走有线网络的，比较稳定。</p><hr><h3 id="回收速度"><a href="#回收速度" class="headerlink" title="回收速度"></a>回收速度</h3><p>Watchdog只是回收链接，不论是Stop还是ForceStop，都其实都是通过SocketIO/TLSIO/ZLibIO来关闭。全双工的TCP链接已经不存在了，所以会卡在<strong>FIN-WAIT-1</strong>，所以别忘了调整TCP参数。具体可以参考：<a href="/2013/10/30/TCP参数调整">Linux TCP参数调整</a></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TCP链接回收&lt;/strong&gt;是所有长连接服务器必须要面对的问题，我前面的有些文章其实已经涉及到该方面的知识。但是本篇文章主要分析和讲述tigase在移动网络中，链接的回收原理及优化方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;心跳&quot;&gt;&lt;a href=&quot;#心跳&quot; class=&quot;headerlink&quot; title=&quot;心跳&quot;&gt;&lt;/a&gt;心跳&lt;/h3&gt;&lt;p&gt;心跳的作用是告诉对方，自己还活着；当然有人说还可以判断链路是否可到达，但是我觉得在JAVA层面这个不是很靠谱。因为当数据刷新到TCP的缓冲区之后，就成功返回了。这也正是即使使用TCP，还需要在业务层封装各种ACK包的原因，即使TCP是可靠链接，它的可靠我理解为是传输时可靠，而非传输前往buffer中写数据的可靠。&lt;/p&gt;
&lt;p&gt;在开始之前，首先明确两个概念。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1. S2S:服务器与服务器之间的通信，网络稳定，基本在同一个IDC内部，当然也有可能跨IDC。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2. C2S:移动设备与服务器之间的通信，网络不稳定。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="IM" scheme="https://www.yangguo.info/tags/IM/"/>
    
  </entry>
  
  <entry>
    <title>Java观察者模式笔记</title>
    <link href="https://www.yangguo.info/2014/09/02/Java%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E7%AC%94%E8%AE%B0/"/>
    <id>https://www.yangguo.info/2014/09/02/Java观察者模式笔记/</id>
    <published>2014-09-02T09:37:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Java观察者模式的实现与对比</p></blockquote><p>观察者模式作为一个耳熟能详的设计概念，早已被绝大多数的程序员所熟悉。虽然<code>Observer</code>和<code>Observable</code>从<strong>JDK1.0</strong>起就已经存在，但是到现在它们还是那样的难用，至少和<code>Guava</code>的<code>EventBus</code>比较起来。传统的发布订阅模式，主要是为了解决进程内事件的分发，从而去掉了显示的注册方式，从而是组件之间可以更好的解耦。现在的发布订阅随着消息中间件的流行，早已实现了跨进程通信。本文主要是对比<strong>JDK</strong>和<strong>Guava</strong>观察者模式的实现区别，如果是玩<strong>Android</strong>的朋友，还可以看看<a href="https://github.com/square/otto" target="_blank" rel="noopener">square/otto</a>(专门为Android平台进行了优化的Guava EventBus库)、<a href="https://github.com/greenrobot/EventBus" target="_blank" rel="noopener">greenrobot/EventBus</a>。</p><a id="more"></a><h3 id="JDK观察者模式实现"><a href="#JDK观察者模式实现" class="headerlink" title="JDK观察者模式实现"></a>JDK观察者模式实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Student</span> <span class="keyword">extends</span> <span class="title">Observable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String course;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String ENGLISH = <span class="string">"english"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String HISTORY = <span class="string">"history"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String MATH = <span class="string">"math"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Student</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getState</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> course;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">changeState</span><span class="params">(String course)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.course != course) &#123;</span><br><span class="line">            <span class="keyword">this</span>.course = course;</span><br><span class="line">            <span class="keyword">this</span>.setChanged();</span><br><span class="line">            <span class="keyword">if</span> (ENGLISH == course) &#123;</span><br><span class="line">                <span class="keyword">this</span>.notifyObservers(<span class="string">"英语课"</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (HISTORY == course) &#123;</span><br><span class="line">                <span class="keyword">this</span>.notifyObservers(<span class="string">"历史课"</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (MATH == course) &#123;</span><br><span class="line">                <span class="keyword">this</span>.notifyObservers(<span class="string">"数学课"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.notifyObservers(<span class="string">"同上一节课"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Teacher</span> <span class="keyword">implements</span> <span class="title">Observer</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Teacher</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>();</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(Observable o, Object arg)</span> </span>&#123;</span><br><span class="line">        Student student = (Student) o;</span><br><span class="line">        <span class="comment">//获取被观察对象当前的状态</span></span><br><span class="line">        System.out.println(name + <span class="string">"被通知："</span> + student.name + <span class="string">"正在上"</span> + student.getState() + <span class="string">"课"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ObserverDemo</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//被观察者</span></span><br><span class="line">        Student student = <span class="keyword">new</span> Student(<span class="string">"杨果"</span>);</span><br><span class="line">        <span class="comment">//观察员：李老师</span></span><br><span class="line">        Teacher teacher1 = <span class="keyword">new</span> Teacher(<span class="string">"李老师"</span>);</span><br><span class="line">        <span class="comment">//观察员：王老师</span></span><br><span class="line">        Teacher teacher2 = <span class="keyword">new</span> Teacher(<span class="string">"王老师"</span>);</span><br><span class="line">        <span class="comment">//观察员：陈老师</span></span><br><span class="line">        Teacher teacher3 = <span class="keyword">new</span> Teacher(<span class="string">"陈老师"</span>);</span><br><span class="line">        <span class="comment">//向被观察对象注册观察员</span></span><br><span class="line">        <span class="comment">//为学生注册观察员：李老师，王老师，陈老师</span></span><br><span class="line">        student.addObserver(teacher1);</span><br><span class="line">        student.addObserver(teacher2);</span><br><span class="line">        student.addObserver(teacher3);</span><br><span class="line">        <span class="comment">//更改被观察对象的状态</span></span><br><span class="line">        student.changeState(Student.HISTORY);</span><br><span class="line">        student.changeState(Student.MATH);</span><br><span class="line">        student.changeState(Student.ENGLISH);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Guava观察者实现"><a href="#Guava观察者实现" class="headerlink" title="Guava观察者实现"></a>Guava观察者实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EventBusTest</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EventListener1</span> </span>&#123;</span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Event1:"</span> + message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EventListener2</span> </span>&#123;</span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Event2:"</span> + message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EventListener3</span> </span>&#123;</span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Event3:"</span> + message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EventListener4</span> </span>&#123;</span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">subscribe</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Event4:"</span> + message);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 注意继承问题</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MultipleListener</span> </span>&#123;</span><br><span class="line">        <span class="keyword">public</span> Integer lastInteger;</span><br><span class="line">        <span class="keyword">public</span> Long lastLong;</span><br><span class="line">        <span class="keyword">public</span> Number lastNumber;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenInteger</span><span class="params">(Integer event)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Integer:"</span> + event);</span><br><span class="line">            lastInteger = event;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenLong</span><span class="params">(Long event)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Long:"</span> + event);</span><br><span class="line">            lastLong = event;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listenNumber</span><span class="params">(Number event)</span> </span>&#123;</span><br><span class="line">            System.out.println(<span class="string">"Number:"</span> + event);</span><br><span class="line">            lastNumber = event;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Integer <span class="title">getLastInteger</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> lastInteger;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Long <span class="title">getLastLong</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> lastLong;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeadEventListener</span> </span>&#123;</span><br><span class="line">        <span class="meta">@Subscribe</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">(DeadEvent event)</span> </span>&#123;</span><br><span class="line">            System.out.println(event.getEvent());</span><br><span class="line">            System.out.println(event.getSource());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 测试同步事件总线</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSyncEventBus</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        EventBus eventBus = <span class="keyword">new</span> EventBus();</span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener1());<span class="comment">//注册事件</span></span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener2());<span class="comment">//注册事件</span></span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener3());<span class="comment">//注册事件</span></span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener4());<span class="comment">//注册事件</span></span><br><span class="line">        eventBus.post(<span class="string">"hello word"</span>);<span class="comment">// 触发事件处理</span></span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"Game over"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 测试异步事件总线</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testAysncEventBus</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        AsyncEventBus eventBus = <span class="keyword">new</span> AsyncEventBus(Executors.newFixedThreadPool(<span class="number">3</span>));</span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener1());</span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener2());</span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener3());</span><br><span class="line">        eventBus.register(<span class="keyword">new</span> EventListener4());</span><br><span class="line">        eventBus.post(<span class="string">"hello word"</span>);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"Game over"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 测试多事件监听器</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testMultipleEventBus</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        EventBus eventBus = <span class="keyword">new</span> EventBus();</span><br><span class="line">        MultipleListener multiListener = <span class="keyword">new</span> MultipleListener();</span><br><span class="line"></span><br><span class="line">        eventBus.register(multiListener);</span><br><span class="line"></span><br><span class="line">        eventBus.post(<span class="keyword">new</span> Integer(<span class="number">100</span>));</span><br><span class="line">        eventBus.post(<span class="keyword">new</span> Long(<span class="number">800</span>));</span><br><span class="line"></span><br><span class="line">        Assert.assertEquals(multiListener.getLastInteger(), <span class="keyword">new</span> Integer(<span class="number">100</span>));</span><br><span class="line">        Assert.assertEquals(multiListener.getLastLong(), <span class="keyword">new</span> Long(<span class="number">800L</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 测试Dead Event</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeadEventBus</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        EventBus eventBus = <span class="keyword">new</span> EventBus();</span><br><span class="line"></span><br><span class="line">        DeadEventListener deadEventListener = <span class="keyword">new</span> DeadEventListener();</span><br><span class="line">        eventBus.register(deadEventListener);</span><br><span class="line"></span><br><span class="line">        eventBus.post(<span class="string">"hello word"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;Java观察者模式的实现与对比&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;观察者模式作为一个耳熟能详的设计概念，早已被绝大多数的程序员所熟悉。虽然&lt;code&gt;Observer&lt;/code&gt;和&lt;code&gt;Observable&lt;/code&gt;从&lt;strong&gt;JDK1.0&lt;/strong&gt;起就已经存在，但是到现在它们还是那样的难用，至少和&lt;code&gt;Guava&lt;/code&gt;的&lt;code&gt;EventBus&lt;/code&gt;比较起来。传统的发布订阅模式，主要是为了解决进程内事件的分发，从而去掉了显示的注册方式，从而是组件之间可以更好的解耦。现在的发布订阅随着消息中间件的流行，早已实现了跨进程通信。本文主要是对比&lt;strong&gt;JDK&lt;/strong&gt;和&lt;strong&gt;Guava&lt;/strong&gt;观察者模式的实现区别，如果是玩&lt;strong&gt;Android&lt;/strong&gt;的朋友，还可以看看&lt;a href=&quot;https://github.com/square/otto&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;square/otto&lt;/a&gt;(专门为Android平台进行了优化的Guava EventBus库)、&lt;a href=&quot;https://github.com/greenrobot/EventBus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;greenrobot/EventBus&lt;/a&gt;。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="设计" scheme="https://www.yangguo.info/tags/%E8%AE%BE%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>Haproxy之websocket的负载均衡方案</title>
    <link href="https://www.yangguo.info/2014/06/25/haproxy%E5%AE%9E%E7%8E%B0websocket%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/"/>
    <id>https://www.yangguo.info/2014/06/25/haproxy实现websocket负载均衡/</id>
    <published>2014-06-25T02:03:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近用websokcet写了一套简单的内部聊天服务，我选择了简单易用的haproxy实现负载均衡。</p></blockquote><h3 id="How-does-websocket-work"><a href="#How-does-websocket-work" class="headerlink" title="How does websocket work ?"></a>How does websocket work ?</h3><p>通常，一个websocket请求的HTTP头如下所示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">GET ws://ws.im.yangguo.info/ws HTTP/1.1</span><br><span class="line">Host: test.ws.im.yunma1688.com</span><br><span class="line">Connection: Upgrade</span><br><span class="line">Pragma: no-cache</span><br><span class="line">Cache-Control: no-cache</span><br><span class="line">Upgrade: websocket</span><br><span class="line">Origin: http://short.im.yangguo.info</span><br><span class="line">Sec-WebSocket-Version: 13</span><br><span class="line">DNT: 1</span><br><span class="line">User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36</span><br><span class="line">Accept-Encoding: gzip, deflate, sdch</span><br><span class="line">Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,de-DE;q=0.4,de;q=0.2,zh-TW;q=0.2</span><br><span class="line">Sec-WebSocket-Key: zGYcUVMijj7ihvhLCEegZQ==</span><br><span class="line">Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits</span><br></pre></td></tr></table></figure><p>这里最核心的部分就是<code>Connection: Upgrade</code>头，它让client端知道server端会改变协议，变成如<code>Upgrade: websocket</code>header中所述的协议。</p><a id="more"></a><p>如果server端有提供websocket协议的能力，那么它就会返回如下的内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">General:</span><br><span class="line">Request URL:ws://ws.im.yangguo.info/ws</span><br><span class="line">Request Method:GET</span><br><span class="line">Status Code:101 Switching Protocols</span><br><span class="line"></span><br><span class="line">Response Headers:</span><br><span class="line">view source</span><br><span class="line">Connection:Upgrade</span><br><span class="line">Sec-WebSocket-Accept:DVDuNyg5QVQp78vY6Ts9/uXsAWE=</span><br><span class="line"></span><br><span class="line">Upgrade:websocket</span><br></pre></td></tr></table></figure><p>Status code 101表示协议切换成功(from http to websocket)。</p><hr><h3 id="HAProxy-and-Websockets"><a href="#HAProxy-and-Websockets" class="headerlink" title="HAProxy and Websockets"></a>HAProxy and Websockets</h3><p>如上所示，websockets嵌入了两种协议：</p><ol><li>HTTP：在websocket启动时</li><li>TCP：websocket数据交换</li></ol><p>任何时候HAProxy必须在一条没有被打断的TCP链路上支持websockets中的两种协议，这里有两件事情需要注意：</p><ol><li>能够从HTTP切换到TCP并且链路不能断开。</li><li>同时支持两种协议的超时管理器</li></ol><p>幸运的是，HAProxy能够完美的解决上面的两个要求，并且支持多种的websockets负载均衡模式。它不但可以将流量转发到不到的后端机器，并且还可以执行健康检查(仅限链路建立阶段)。</p><p>下图详细说明了每个阶段发生了什么和每个阶段的涉及到的timeout：</p><p><img src="/images/timeout_websocket.png" alt="timeout_haproxy"></p><p>在链路建立阶段，HAProxy以HTTP模式运行，处理七层的的信息。它会自动检测连接：升级交换，并准备好切换到隧道模式如果升级协商成功。在这个阶段会涉及到3个timeout:</p><ol><li>client timeout:client端不活跃的时间</li><li>connect timeout:允许TCP链接建立的时间</li><li>server timeout:允许server端处理请求的时间</li></ol><p>如果一切顺利，websocket建立成功，然后HAProxy故障转义到tunnel模式，此时HTTP层面在没有任何数据传输。因此该阶段只涉及到一个timeout</p><ol><li>tunnel timeout:优先于client和server端超时</li></ol><p>connect timeout将不再使用，因为TCP链路已经建立完成。</p><h4 id="调度模式"><a href="#调度模式" class="headerlink" title="调度模式"></a>调度模式</h4><p><strong>Haproxy</strong>，负载均衡调度模式有如下几种:</p><ol><li>roundrobin，表示简单的轮询，这个不多说，这个是负载均衡基本都具备的；</li><li>static-rr，表示根据权重;</li><li>leastconn，表示最少连接者先处理；</li><li>source，表示根据请求源IP；</li><li>uri，表示根据请求的URI；</li><li>url_param，表示根据请求的URl参数’balance url_param’ requires an URL parameter name</li><li>hdr(name)，表示根据HTTP请求头来锁定每一次HTTP请求；</li><li>rdp-cookie(name)，表示根据据cookie(name)来锁定并哈希每一次TCP请求。</li></ol><h4 id="整体配置"><a href="#整体配置" class="headerlink" title="整体配置"></a>整体配置</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">global</span><br><span class="line">  log 127.0.0.1 local3    #local3是设备，对应于 /etc/rsyslog.conf中的配置，默认回收info的日志级别</span><br><span class="line">  daemon</span><br><span class="line">defaults</span><br><span class="line">  mode http</span><br><span class="line">  log global</span><br><span class="line">  option httplog</span><br><span class="line">  option  http-server-close</span><br><span class="line">option  dontlognull</span><br><span class="line">  option  redispatch</span><br><span class="line">  option  contstats</span><br><span class="line">  retries 3</span><br><span class="line">  backlog 10000</span><br><span class="line">  timeout client          25s</span><br><span class="line">  timeout connect          5s</span><br><span class="line">  timeout server          25s</span><br><span class="line"># timeout tunnel available in ALOHA 5.5 or HAProxy 1.5-dev10 and higher</span><br><span class="line">  timeout tunnel        15m</span><br><span class="line">  timeout http-keep-alive  1s</span><br><span class="line">  timeout http-request    15s</span><br><span class="line">  timeout queue           30s</span><br><span class="line">  timeout tarpit          60s</span><br><span class="line">  default-server inter 3s rise 2 fall 3</span><br><span class="line">  option forwardfor</span><br><span class="line"></span><br><span class="line">frontend http</span><br><span class="line">    bind *:80</span><br><span class="line">## routing based on Host header</span><br><span class="line"> acl host_ws hdr_beg(Host) -i ws.yangguo.info</span><br><span class="line">  use_backend bk_ws if host_ws</span><br><span class="line">## routing based on websocket protocol header</span><br><span class="line">  acl hdr_connection_upgrade hdr(Connection)  -i upgrade</span><br><span class="line">  acl hdr_upgrade_websocket  hdr(Upgrade)     -i websocket</span><br><span class="line">  use_backend bk_ws if hdr_connection_upgrade hdr_upgrade_websocket</span><br><span class="line"></span><br><span class="line">acl host_short hdr_beg(Host) -i short.im.yangguo.info</span><br><span class="line">use_backend bk_short if host_short</span><br><span class="line"></span><br><span class="line">backend bk_ws</span><br><span class="line">    balance roundrobin</span><br><span class="line">    server server1 192.168.199.125:60000 maxconn 10000 weight 10 cookie server1  check</span><br><span class="line">server server1 192.168.199.126:60000 maxconn 10000 weight 10 cookie server1  check</span><br><span class="line"></span><br><span class="line">backend bk_short</span><br><span class="line">    balance roundrobin</span><br><span class="line">    server server1 192.168.199.125:8082 cookie server1 check</span><br><span class="line">    server server1 192.168.199.126:8082 cookie server1 check</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近用websokcet写了一套简单的内部聊天服务，我选择了简单易用的haproxy实现负载均衡。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;How-does-websocket-work&quot;&gt;&lt;a href=&quot;#How-does-websocket-work&quot; class=&quot;headerlink&quot; title=&quot;How does websocket work ?&quot;&gt;&lt;/a&gt;How does websocket work ?&lt;/h3&gt;&lt;p&gt;通常，一个websocket请求的HTTP头如下所示:&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;GET ws://ws.im.yangguo.info/ws HTTP/1.1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Host: test.ws.im.yunma1688.com&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Connection: Upgrade&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Pragma: no-cache&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Cache-Control: no-cache&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Upgrade: websocket&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Origin: http://short.im.yangguo.info&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Sec-WebSocket-Version: 13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;DNT: 1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Accept-Encoding: gzip, deflate, sdch&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,de-DE;q=0.4,de;q=0.2,zh-TW;q=0.2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Sec-WebSocket-Key: zGYcUVMijj7ihvhLCEegZQ==&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;这里最核心的部分就是&lt;code&gt;Connection: Upgrade&lt;/code&gt;头，它让client端知道server端会改变协议，变成如&lt;code&gt;Upgrade: websocket&lt;/code&gt;header中所述的协议。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="负载均衡" scheme="https://www.yangguo.info/tags/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/"/>
    
  </entry>
  
  <entry>
    <title>Kryo2.22详解</title>
    <link href="https://www.yangguo.info/2014/05/19/Kryo2.22%E7%BF%BB%E8%AF%91/"/>
    <id>https://www.yangguo.info/2014/05/19/Kryo2.22翻译/</id>
    <published>2014-05-19T13:03:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在2.22版本修正了许多之前反馈的问题，提高稳定性和性能。 它还引入了许多新的功能，最主要的是，它可以使用Unsafe的方式的直接读取和写入对象的内存。 这中绝对是最快的方式序列化方式，特别是在操作大型原始数组的时候。<br>Maven仓库中的JAR文件现在包含一个ObjectWeb的“阴影”版本ASM库，以避免与你项目中的ASM库出现兼容问题，从而导致你的应用程序粗线冲突。 这里不再需要一个单独的阴影jar了。</p></blockquote><p><code>由于本篇文章是之前翻译的，最后迁移到该博客。在迁移过程中可能出现某些地方出错，并且由于水平有限，如果发生上面的情况，请大家发邮件告诉我，谢谢！</code></p><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p><strong>Kryo</strong>是一种快速，高效的对象图序列化的Java框架。 该项目的目标是速度，效率，以及一个易于使用的API。该项目对那些在任何时间，对象需要被持久化，无论是文件，数据库，或通过网络的项目都是适用的。Kryo还可以自动实现深浅的复制/克隆。 就是直接复制一个对象对象到例外一个对象，而不是对象-&gt;字节-&gt;对象。<br>本文档是Kryo的V2版本。如果V1版本感兴趣，见<a href="https://github.com/EsotericSoftware/kryo/wiki/Documentation-for-Kryo-version-1.x" target="_blank" rel="noopener">V1Documentation</a>。<br>如果您打算使用Kryo进行网络通信，该<a href="https://github.com/EsotericSoftware/kryonet" target="_blank" rel="noopener">KryoNet</a>项目可能对你有用。</p><hr><h3 id="内容"><a href="#内容" class="headerlink" title="内容"></a>内容</h3><ul><li>Quickstart</li><li>IO</li><li>Unsafe-based IO</li><li>Serializers</li><li>Registration</li><li>Default serializers</li><li>FieldSerializer</li><li>KryoSerializable</li><li>Reading and writing</li><li>References</li><li>Object creation</li><li>Copying/cloning</li><li>Context</li><li>Compression and encryption</li><li>Chunked encoding</li><li>Compatibility</li><li>Interoperability</li><li>Stack size</li><li>Threading</li><li>Logging</li><li>Integration with Maven</li><li>Projects using Kryo</li></ul><a id="more"></a><hr><h3 id="Quickstart快速入门"><a href="#Quickstart快速入门" class="headerlink" title="Quickstart快速入门"></a>Quickstart快速入门</h3><p>让我们从头开始来看看该库如何使用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Kryokryo=newKryo();</span><br><span class="line">Outputoutput=newOutput(newFileOutputStream(<span class="string">"file.bin"</span>));</span><br><span class="line">SomeClasssomeObject=...</span><br><span class="line">kryo.writeObject(output,someObject);</span><br><span class="line">output.close();</span><br><span class="line">Inputinput=newInput(newFileInputStream(<span class="string">"file.bin"</span>));</span><br><span class="line">SomeClasssomeObject=kryo.readObject(input,SomeClass.class);</span><br><span class="line">input.close();</span><br></pre></td></tr></table></figure><p>该Kryo类协调整个序列化。 输出和输入类处理缓冲字节和用可选的方式将其刷新到流。<br>本文档的其余部分详细介绍它是如何工作的和该库的高级用法。</p><hr><h3 id="IO"><a href="#IO" class="headerlink" title="IO"></a>IO</h3><p>输出类是OutputStream，它将数据写入一个字节数组缓冲区。 这个缓冲器中的内容可以直接获得和使用，如果一个字节数组是需要的。 如果输出是一个给定的OutputStream，当字节缓冲区满时会将其刷新到该输出流。 Output有很多方法能够高效地将原语和字符串转为字节。 它提供了类似DataOutputStream，BufferedOutputSteam，FilterOutputStream，和ByteArrayOutputStream类的功能。</p><p>因为Output buffers写入到OutputStream时，一定要调用flush()或close()当写入完成后，从而使缓冲字节写入底层流。</p><p>输入类是一个InputStream,它能够从一个字节数组缓冲区读取数据。这个buffer可以直接设置成缓冲区，如果需要从这个buffer中读取数据。 如果输入是一个给定的InputStream，这个buffer将会被填满直到buffer空间耗尽。Input有很多方法能够高效地读取原语和字符串通过字节的方式。  它提供了类似DataInputStream，BufferedInputStream，FilterInputStream，和ByteArrayInputStream类的功能。</p><p>如果读取或者写入不是一个字节数组，只需提供相应的InputStream和OutputStream就行了。</p><hr><h3 id="Unsafe-based-IO-不安全型的IO"><a href="#Unsafe-based-IO-不安全型的IO" class="headerlink" title="Unsafe-based IO 不安全型的IO"></a>Unsafe-based IO 不安全型的IO</h3><p>Kryo提供了额外的IO类，它是基于sun.misc.Unsafe类公开的功能。 这些类是UnsafeInput，UnsafeOutput。对于来自Kryo的Input和Output，它们可以作为在那些支持sun.misc.Unsafe平台上的一种IO替换方案。</p><p>对于那些需要在物理内存和堆外内存上直接序列化和反序列的情况，这里有两个专用类UnsafeMemoryInputUnsafeMemoryOutput，它们可以替换平常的Input和Output类来实现该功能。<br>使用Unsafe-based IO可能导致在一个相当显著的性能提升，这主要取决于你的应用程序。 特别是那些需要序列化大型原始数组作为你的对象图的一部分的情况。</p><p><strong>关于使用Unsafe-based IO的免责申明 </strong></p><p>Unsafe-based IO不能100%的与Kryo的Input和Output 流兼容，当涉及到序列化数据的二进制格式时！<br>这意味着它们是由Unsafe-based产生的输出流只能由Unsafe-based输入流读取，而不是被通常的input stream读取。 这同样适用于相反的方向：通常的输出流中的数据不能被Unsafe-based IO读取。</p><p>只要序列化和反序列化两边都使用Unsafe IO或者在同样架构的处理器（更准确地说，如果字节顺序和内部表示整数和浮点类型是一样的）上使用，它们就是安全的。</p><hr><h3 id="Serializers"><a href="#Serializers" class="headerlink" title="Serializers"></a>Serializers</h3><p>Kryo是一个序列化框架。 它不会强求是否有一个schema或者关心数据到底是写入还是读取。 这是留给序列化器本身。 Serializers在默认情况下各种提供读取和写入数据的方式。 如果这些仍不符合特定的需要，它们可以部分或者全部被替换。 所提供的序列化器可以读写大多数对象，但是如果有必要，写一个新的序列化也是很容易。 序列化器的抽象类，定义了从对象到字节和字节到对象的方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ColorSerializer</span> <span class="keyword">extends</span> <span class="title">Serializer</span>&lt;<span class="title">Color</span>&gt;</span>&#123;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span><span class="params">(Kryo kryo,Output output,Color object)</span></span>&#123;</span><br><span class="line">output.writeInt(object.getRGB());</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> Color <span class="title">read</span><span class="params">(Kryo kryo,Input input,Class&lt;T&gt; type&#123;</span></span></span><br><span class="line"><span class="function"><span class="params">return new Color(input.readInt()</span>,<span class="keyword">true</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>序列化器具有可实现的两种方法。 write()将对象转化为字节。 read()创建该对象的一个新实例，并从输入中读取信息来填充它。</p><p>Kryo实例可用于写入和读取嵌套对象。 如果Kryo在read()中读取了嵌套对象，那么kryo.reference()必须首先与父对象一起调用，假如该嵌套对象引用了该父对象。假如嵌套对象没有引用父对象，Kryo不处理嵌套对象或者对象引用不存在，那么kryo.reference()是没有必要被调用的。 如果嵌套的对象使用相同的序列化器，序列化器必须是可重入的（什么是可重入的对象序列化? 就是说对象任意时候都可以进行序列化,包括保存和载入,而且不会影响当前程序运行和对象状态的异常，也就是说该序列化器不应该有特定的状态）。</p><p>代码不应该直接使用serializer，而是应该使用Kryo类的read和write方法来替代。 这样就能通过Kryo来编排序列化和处理新功能，如引用和空对象。</p><p>默认情况下，序列化程序并不需要处理null对象。 Kryo会根据需要通过一个byte来表示null或not null。如果一个serializer希望更高效和能够处理null，它可以调用Serializer#setAcceptsNull(true) 。 这个方法同样可以用来避免写入表示null的字节，当你知道一种类型的所有实例将永远不会是空的时候。</p><hr><h3 id="Registration注册"><a href="#Registration注册" class="headerlink" title="Registration注册"></a>Registration注册</h3><p>当Kryo输出一个对象的实例时，它首先会输出该对象的Class的标识符。 默认情况下，类的完全限定名称会被写入，后面紧接的是对象的字节。 随后出现的是该对象图中的对象类型，它们通过可变长度的整数表示。 将类名写入序列化文件是有点效率低下的，因此类可以事先注册，从而避免写入类名：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   kryo.register(SomeClass.class);</span><br><span class="line">   <span class="comment">// ...</span></span><br><span class="line">   Output output = ...</span><br><span class="line">   SomeClass someObject = ...</span><br><span class="line">   kryo.writeObject(output, someObject);</span><br></pre></td></tr></table></figure><p>这里SomeClass的注册到了Kryo，该类与一个int型的ID相互关联。 当Kryo输出SomeClass实例时，它会输出这个整数ID。 这样比输出类的名称更高效，但要求被序列的类在序列化之前知道。 反序列化过程中，注册类id必须和序列化过程中的该类的id一致。 上面显示的注册方法分配下一个可用，最小的整数ID，这就意味着类的注册顺序非常重要。 该ID也可以明确指定，从而使顺序不重要：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   kryo.register(SomeClass.class, <span class="number">0</span>);</span><br><span class="line">   kryo.register(AnotherClass.class, <span class="number">1</span>);</span><br><span class="line">   kryo.register(YetAnotherClass.class, <span class="number">2</span>);</span><br></pre></td></tr></table></figure><p>该ID很小时特别是正整数时，是非常高效的。 负整数ID不能高效的序列化。 -1和-2是保留的，不能使用。</p><p>已注册和未注册的类可以混合使用。 所有的基本数据类型，基础对象和String是默认注册的。</p><p>Kryo#setRegistrationRequired可以设置为true，那么任何未注册的类在序列化和反序列化都会抛出一个异常。 这样可以防止一个应用程序意外地使用类名的字符串。</p><p>如果使用未注册的类，缩短短包是一种优化方案。</p><hr><h3 id="Default-serializers-默认的序列化器"><a href="#Default-serializers-默认的序列化器" class="headerlink" title="Default serializers 默认的序列化器"></a>Default serializers 默认的序列化器</h3><p>写完类标识符之后，Kryo会使用序列化器将对象转化成字节写入。 当一个类被在注册时可以指定一个序列化方式的的实例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">kryo.register(SomeClass.class, <span class="keyword">new</span> SomeSerializer());</span><br><span class="line">kryo.register(AnotherClass.class, <span class="keyword">new</span> AnotherSerializer());</span><br></pre></td></tr></table></figure><p>如果一个类没有注册或没有指定序列化器，会根据类的类型自动选择一个默认的序列化器。 下面的类含有一个默认的序列化器：</p><table><thead><tr><th style="text-align:left">boolean</th><th style="text-align:left">Boolean</th><th style="text-align:left">byte</th><th style="text-align:left">Byte</th><th style="text-align:left">char</th></tr></thead><tbody><tr><td style="text-align:left">Character</td><td style="text-align:left">short</td><td style="text-align:left">Short</td><td style="text-align:left">int</td><td style="text-align:left">Integer</td></tr><tr><td style="text-align:left">long</td><td style="text-align:left">Long</td><td style="text-align:left">float</td><td style="text-align:left">Float</td><td style="text-align:left">double</td></tr><tr><td style="text-align:left">Double</td><td style="text-align:left">byte[]</td><td style="text-align:left">String</td><td style="text-align:left">BigInteger</td><td style="text-align:left">BigDecimal</td></tr><tr><td style="text-align:left">Collection</td><td style="text-align:left">Date</td><td style="text-align:left">Collections.emptyList</td><td style="text-align:left">Collections.singleton</td><td style="text-align:left">Map</td></tr><tr><td style="text-align:left">StringBuilder</td><td style="text-align:left">TreeMap</td><td style="text-align:left">Collections.emptyMap</td><td style="text-align:left">Collections.emptySet</td><td style="text-align:left">KryoSerializable</td></tr><tr><td style="text-align:left">StringBuffer</td><td style="text-align:left">Class</td><td style="text-align:left">Collections.singletonList</td><td style="text-align:left">Collections.singletonMap</td><td style="text-align:left">Currency</td></tr><tr><td style="text-align:left">Calendar</td><td style="text-align:left">TimeZone</td><td style="text-align:left">Enum</td><td style="text-align:left">EnumSet</td></tr></tbody></table><p>可以为指定的类添加默认的序列化器：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   kryo.addDefaultSerializer(SomeClass.class, SomeSerializer.class);</span><br><span class="line">   <span class="comment">// ...</span></span><br><span class="line">   Output output = ...</span><br><span class="line">   SomeClass someObject = ...</span><br><span class="line">   kryo.writeObject(output, someObject);</span><br></pre></td></tr></table></figure><p>也可以使用DefaultSerializer注解来添加默认的序列化器：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@DefaultSerializer</span>(SomeClassSerializer.class)</span><br><span class="line">   <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SomeClass</span> </span>&#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>如果一个类没有默认的序列化器与之相匹配，则默认情况下使用<a href="https://github.com/EsotericSoftware/kryo#FieldSerializer" target="_blank" rel="noopener">FieldSerializer</a>。 这也是可以改变：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">kryo.setDefaultSerializer(AnotherGenericSerializer.class);</span><br></pre></td></tr></table></figure><p>一些序列化器允许提供额外的信息，从而减少字节的输出：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   FieldSerializer someClassSerializer = <span class="keyword">new</span> FieldSerializer(kryo, SomeClass.class);</span><br><span class="line">   CollectionSerializer listSerializer = <span class="keyword">new</span> CollectionSerializer();</span><br><span class="line">   listSerializer.setElementClass(String.class);</span><br><span class="line">   listSerializer.setElementsCanBeNull(<span class="keyword">false</span>);</span><br><span class="line">   someClassSerializer.getField(<span class="string">"list"</span>).setClass(LinkedList.class, listSerializer);</span><br><span class="line">   kryo.register(SomeClass.class, someClassSerializer);</span><br><span class="line">   <span class="comment">// ...</span></span><br><span class="line">   SomeClass someObject = ...</span><br><span class="line">   someObject.list = <span class="keyword">new</span> LinkedList();</span><br><span class="line">   someObject.list.add(<span class="string">"thishitis"</span>);</span><br><span class="line">   someObject.list.add(<span class="string">"bananas"</span>);</span><br><span class="line">   kryo.writeObject(output, someObject);</span><br></pre></td></tr></table></figure><p>在这个例子中，FieldSerializer序列化器将用于SomeClass的。 从FieldSerializer的配置可以看出，“list”字段将永远是一个LinkedList，并使用指定的CollectionSerializer。 该CollectionSerializer被配置成每个元素将是一个String，并没有任何元素都为null。 这使得序列化器更有效率。 在这种情况下，列表中的每个元素大概需要2〜3个字节。</p><hr><h3 id="FieldSerializer"><a href="#FieldSerializer" class="headerlink" title="FieldSerializer"></a>FieldSerializer</h3><p>默认情况下，大多数的类最终使用FieldSerializer。 它本质上是手写的序列化方式，但是它会自动执行。FieldSerializer直接对对象的字段进行转换。 如果字段是public，protected，或默认的访问（包私有），使用最快速度生成字节码（见<a href="https://github.com/EsotericSoftware/reflectasm" target="_blank" rel="noopener">ReflectASM</a> ）。 对于私有字段，setAccessible和缓存反射状态，速度还是相当快的。<br>还提供了其他用途的序列化器，如的BeanSerializer，TaggedFieldSerializer和CompatibleFieldSerializer。 额外的序列化器，在GitHub上是一个单独的项目，<a href="https://github.com/magro/kryo-serializers" target="_blank" rel="noopener">kryo-serializers</a>。</p><hr><h3 id="KryoSerializable"><a href="#KryoSerializable" class="headerlink" title="KryoSerializable"></a>KryoSerializable</h3><p>虽然FieldSerializer对于大多数类来说是理想的，但是有时你也想很方便的实现自己的序列化方式。 这可以通过实现KryoSerializable接口（类似于JDK的java.io.Externalizable接口）来完成。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SomeClass</span> <span class="keyword">implements</span> <span class="title">KryoSerializable</span> </span>&#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span> <span class="params">(Kryo kryo, Output output)</span> </span>&#123;</span><br><span class="line">         <span class="comment">// ...</span></span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">read</span> <span class="params">(Kryo kryo, Input input)</span> </span>&#123;</span><br><span class="line">         <span class="comment">// ...</span></span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Reading-and-writing"><a href="#Reading-and-writing" class="headerlink" title="Reading and writing"></a>Reading and writing</h3><p>Kryo有三套方法集来读取和写入的对象。</p><p>如果对象的具体类不知道且对象可能为null：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">kryo.writeClassAndObject(output, object);</span><br><span class="line">   <span class="comment">// ...</span></span><br><span class="line">   Object object = kryo.readClassAndObject(input);</span><br><span class="line">   <span class="keyword">if</span> (object <span class="keyword">instanceof</span> SomeClass) &#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>如果类是已知的且该对象可能为null：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">kryo.writeObjectOrNull(output, someObject);</span><br><span class="line">   <span class="comment">// ...</span></span><br><span class="line">   SomeClass someObject = kryo.readObjectOrNull(input, SomeClass.class);</span><br></pre></td></tr></table></figure><p>如果类是已知的，该对象不能为空：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">kryo.writeObject(output, someObject);</span><br><span class="line">   <span class="comment">// ...</span></span><br><span class="line">   SomeClass someObject = kryo.readObject(input, SomeClass.class);</span><br></pre></td></tr></table></figure><hr><h3 id="References"><a href="#References" class="headerlink" title="References"></a>References</h3><p>默认情况下，对象图中出现的对象会在首次已一个整数序号存储下来。 这样就能对多个引用指向同一个对象和循环图进行序列化。 这有一个小的开销，但是可以关闭以节省空间，如果你不需要保存引用：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   kryo.setReferences(<span class="keyword">false</span>);</span><br><span class="line">   <span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>当使用Kryo对嵌套对象进行序列化时， kryo.reference()必须在read() 中被调用。 见<a href="https://github.com/EsotericSoftware/kryo#serializers" target="_blank" rel="noopener">Serializers</a>获取更多信息。</p><hr><h3 id="Object-creation"><a href="#Object-creation" class="headerlink" title="Object creation"></a>Object creation</h3><p>对于特定类型的序列化器，会使用Java代码来创建该类型的新实例。 如FieldSerializer序列化器，它是通用的能够创建任何类的实例。 默认情况下，如果一个类有一个无参数的构造函数，然后它是通过ReflectASM来实现invoke或反射，否则将引发异常。 如果无参数的构造函数是私有的，会调用setAccessible方法通过反射来访问它。 如果这是可以接受的，私人的无参数的构造函数是一个很好的方式，让Kryo创建一个类的实例，而不会影响公共API。</p><p>当ReflectASM或反射不能用，Kryo可以被配置为使用InstantiatorStrategy来处理创建类的实例。 Objenesis 提供StdInstantiatorStrategy它使用JVM的特定API来创建一个类的实例，不需要调用任何构造函数都没有。 虽然这个作品多次在JVM上，一个无参的够着函数其实是更方便的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kryo.setInstantiatorStrategy(<span class="keyword">new</span> StdInstantiatorStrategy());</span><br></pre></td></tr></table></figure><p>注意，类必须被设计为以这种方式创建的。 如果一个类希望它的构造函数能够被调用，通过这一机制建立的，它可能是处于未初始化状态。<br>Objenesis也可以使用Java内置的序列化机制创建新的对象。 利用这一点，该类必须实现java.io.Serializable，并且无参的构造函数应该在超类中被调用。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kryo.setInstantiatorStrategy(newSerializingInstantiatorStrategy());</span><br></pre></td></tr></table></figure><p>您也可以编写自己的InstantiatorStrategy。<br>要自定义创建特定类型的方式，一个ObjectInstantiator可以设置。 这将覆盖ReflectASM，reflection，以及InstantiatorStrategy。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Registrationregistration=kryo.register(SomeClass.class);</span><br><span class="line">registration.setObjectInstantiator(...);</span><br></pre></td></tr></table></figure><p>另外，一些序列化器提供了可以被覆盖以自定义对象的创建方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">kryo.register(SomeClass.class, <span class="keyword">new</span> FieldSerializer(kryo, SomeClass.class) &#123;</span><br><span class="line">      <span class="function"><span class="keyword">public</span> Object <span class="title">create</span> <span class="params">(Kryo kryo, Input input, Class type)</span> </span>&#123;</span><br><span class="line">         <span class="keyword">return</span> <span class="keyword">new</span> SomeClass(<span class="string">"some constructor arguments"</span>, <span class="number">1234</span>);</span><br><span class="line">      &#125;</span><br><span class="line">   &#125;);</span><br></pre></td></tr></table></figure><hr><h3 id="Copying-cloning"><a href="#Copying-cloning" class="headerlink" title="Copying/cloning"></a>Copying/cloning</h3><p>序列化库需要特定的知识在如何创建新的实例，获取和设置值，操作对象图等。这所有的一切都需要支持copying objects，因此Kryo自动支持深浅拷贝是顺理成章的事情。 注意，Kryo的拷贝不是序列化和反序列化，而是直接分配。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   SomeClass someObject = ...</span><br><span class="line">   SomeClass copy1 = kryo.copy(someObject);</span><br><span class="line">   SomeClass copy2 = kryo.copyShallow(someObject);</span><br></pre></td></tr></table></figure><p>该序列化类有一个copy 方法来完成这项工作。这些方法能够被忽略，假如拷贝方法没有使用在实现应用程序中特定的序列化方式时。 Kryo的拷贝支持所有的序列化器。 多个引用指向同一个对象和循环引用会自动被框架理。</p><p>类似read()序列化方法， kryo.reference()必须先于Kryo复制子对象被调用。 通过<a href="https://github.com/EsotericSoftware/kryo#Serializers" target="_blank" rel="noopener">Serializers</a>获取更多信息。</p><p>类似KryoSerializable，可以实现KryoCopyable来实现自己的复制：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SomeClass</span> <span class="keyword">implements</span> <span class="title">KryoCopyable</span>&lt;<span class="title">SomeClass</span>&gt; </span>&#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">      <span class="function"><span class="keyword">public</span> SomeClass <span class="title">copy</span> <span class="params">(Kryo kryo)</span> </span>&#123;</span><br><span class="line">         <span class="comment">// Create new instance and copy values from this instance.</span></span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h3><p>Kryo有两个context方法。 getContext()返回一个map，用于存储用户数据 。 因为Kryo实例提供给所有的序列化器使用，这个数据是现成的。 getGraphContext()类似，但它会在每个对象图形序列化和反序列化之后被清除。 这可以很容易地管理每对象图的状态。</p><hr><h3 id="压缩和加密"><a href="#压缩和加密" class="headerlink" title="压缩和加密"></a>压缩和加密</h3><p>Kryo支持流，所以在序列化后字节上压缩和加密是非常简单的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">OutputStream outputStream = <span class="keyword">new</span> DeflaterOutputStream(<span class="keyword">new</span> FileOutputStream(<span class="string">"file.bin"</span>));</span><br><span class="line">   Output output = <span class="keyword">new</span> Output(outputStream);</span><br><span class="line">   Kryo kryo = <span class="keyword">new</span> Kryo();</span><br><span class="line">   kryo.writeObject(output, object);</span><br><span class="line">   output.close();</span><br></pre></td></tr></table></figure><p>如果需要,可以使用序列化器只压缩或加密对象图字节的一个子集。 例如，请参见DeflateSerializer或BlowfishSerializer。 这些序列化包装其他序列化器来编码和解码的字节。</p><hr><h3 id="分块编码"><a href="#分块编码" class="headerlink" title="分块编码"></a>分块编码</h3><p>有时候先将数据的长度写入然后再将数据写入是有意义的。 如果不知道数据的长度，所有的数据将需要一个缓冲来确定其长度，以确定它的长度，则该长度可以被写入，然后才是数据。这种在防止流和潜在获取非常大的缓冲区方面不是很理想。<br>分块编码通过使用一个小缓冲解决这个问题。 当缓冲器满时，它的长度被写入，然后是数据。 这就是一个数据块。然后缓冲区被清除，这种情况持续下去，直到有没有更多的数据写入。 当一个长度为零的块表示所有块的结束。<br>Kryo提供了一些类，便于分块编码。 OutputChunked是用来写分块数据的。 它继承了Output，所以拥有所有方便的方法来写数据。 当OutputChunked缓冲区满时，它会将数据刷新到一个被包装过的OutputStream中。 该endChunks()方法用来标记一组块的结束。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">OutputStream outputStream = <span class="keyword">new</span> FileOutputStream(<span class="string">"file.bin"</span>);</span><br><span class="line">   OutputChunked output = <span class="keyword">new</span> OutputChunked(outputStream, <span class="number">1024</span>);</span><br><span class="line">   <span class="comment">// Write data to output...</span></span><br><span class="line">   output.endChunks();</span><br><span class="line">   <span class="comment">// Write more data to output...</span></span><br><span class="line">   output.endChunks();</span><br><span class="line">   <span class="comment">// Write even more data to output...</span></span><br><span class="line">   output.close();</span><br></pre></td></tr></table></figure><p>要读取分块的数据，可以使用InputChunked。 它继承了Input，因此它具有所有读取数据的方便方法。 当读取数据时，InputChunked会准确找出数据结束的数据块。 nextChunks()方法的作用是前进到下一组块，即使不是所有的数据要从当前组块集合中读出。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">InputStream outputStream = <span class="keyword">new</span> FileInputStream(<span class="string">"file.bin"</span>);</span><br><span class="line">   InputChunked input = <span class="keyword">new</span> InputChunked(inputStream, <span class="number">1024</span>);</span><br><span class="line">   <span class="comment">// Read data from first set of chunks...</span></span><br><span class="line">   input.nextChunks();</span><br><span class="line">   <span class="comment">// Read data from second set of chunks...</span></span><br><span class="line">   input.nextChunks();</span><br><span class="line">   <span class="comment">// Read data from third set of chunks...</span></span><br><span class="line">   input.close();</span><br></pre></td></tr></table></figure><hr><h3 id="兼容性"><a href="#兼容性" class="headerlink" title="兼容性"></a>兼容性</h3><p>对于某些需求，尤其是长期存储的序列化字节，如何处理那些已经更改类的序列化是非常重要的。 这被称为前向和后向兼容性。 默认情况下，大多数用户类将使用FieldSerializer，它不支持添加，删除或更改类型，对于之前的序列化字节来说是无效字段。 这是可以接受的在许多情况下，例如在网络上发送数据。 如果有必要，可以使用一个简单的序列化器来替换：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kryo.setDefaultSerializer(TaggedFieldSerializer.class);</span><br></pre></td></tr></table></figure><p>TaggedFieldSerializer只序列化有一个@Tag注释的字段。 相比FieldSerializer不够灵活，因为它可以处理大多数类而无需注解，但允许TaggedFieldSerializer添加一个对之前序列化文件来说无效的字段。 如果一个字段被删除就会导致以前序列化的字节失效，所以字段应该用@Deprecated注解表示被删除。</p><p>或者，可以使用CompatibleFieldSerializer，在序列化对象时，会将第一次碰见的class用一个schema来表示，并写入序列化文件。 像FieldSerializer，它可以无需注解序列所有类。 字段可以添加或删除，无需关心以前的序列化字节，但不支持改变一个字段的类型。 这具有一些额外的开销，无论是在速度和空间，相对于FieldSerializer。</p><p>额外的具有向前和向后兼容序列化可能会被开发，例如使用一个外部的，手写模式的序列化程序。</p><hr><h3 id="互通性"><a href="#互通性" class="headerlink" title="互通性"></a>互通性</h3><p>Kryo序列化器默认假设Java将作为反序列化，所以他们并没有明确定义写入格式。 序列化器可以编写一个标准化的格式使别的语言更容易阅读,但这不是默认提供。</p><hr><h3 id="堆大小"><a href="#堆大小" class="headerlink" title="堆大小"></a>堆大小</h3><p>Kryo能够直接访问堆当序列化嵌套对象时。 Kryo确实减少堆栈调用，但是对于极深的对象图，堆溢出可能发生。 这是大多数序列化库，包括Java内置的序列化都面临该问题。 堆大小的调节可以使用-Xss ，但是请注意，这是针对所有线程。 堆太大并且线程很多会导致JVM使用大量内存。</p><hr><h3 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h3><p>Kryo不是线程安全的。 每个线程都应该有自己的Kryo，输入和输出的实例。 此外，byte []的输入用途可以被修改，然后反序列化过程返回到其原始状态，因此相同的字节不应该在多线程中并发使用。</p><hr><h3 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h3><p>Kryo利用了低开销，轻量级的MinLog logging library.。 记录级别可以通过下列方法之一来设置：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Log.ERROR();</span><br><span class="line">   Log.WARN();</span><br><span class="line">   Log.INFO();</span><br><span class="line">   Log.DEBUG();</span><br><span class="line">   Log.TRACE();</span><br></pre></td></tr></table></figure><p>Kryo不会输出INFO （默认值）及以上水平的日志。 DEBUG方便开发过程中使用。 TRACE好调试一个具体问题时使用，但这种级别一般会有太多的信息要输出。</p><p>MinLog支持一个固定的日志级别，这会导致javac来在编译时去掉低于这一水平的日志记录语句。 在Kryo派发zip文件中，“debug”的JAR中被开启。 “production”JAR文件使用一个固定的日志记录级别NONE ，这意味着所有的日志代码已被删除。</p><hr><h3 id="使用Kryo的项目"><a href="#使用Kryo的项目" class="headerlink" title="使用Kryo的项目"></a>使用Kryo的项目</h3><ul><li><a href="http://code.google.com/p/kryonet/" target="_blank" rel="noopener">KryoNet</a> (NIO networking)</li><li><a href="https://github.com/twitter/scalding" target="_blank" rel="noopener">Twitter’s Scalding</a> (Scala API for Cascading)</li><li><a href="https://github.com/twitter/chill" target="_blank" rel="noopener">Twitter’s Chill</a> (Kryo serializers for Scala)</li><li><a href="http://hive.apache.org/" target="_blank" rel="noopener">Apache Hive</a> (query plan serialization)</li><li><a href="https://github.com/datanucleus/type-converter-kryo" target="_blank" rel="noopener">DataNucleus</a> (JDO/JPA persistence framework)</li><li><a href="http://www.cloudpelican.com/" target="_blank" rel="noopener">CloudPelican</a></li><li><a href="http://www.s4.io/" target="_blank" rel="noopener">Yahoo’s S4</a> (distributed stream computing)</li><li><a href="https://github.com/nathanmarz/storm/wiki/Serialization" target="_blank" rel="noopener">Storm</a> (distributed realtime computation system, in turn used by many others)</li><li><a href="https://github.com/nathanmarz/cascalog" target="_blank" rel="noopener">Cascalog</a> (Clojure/Java data processing and querying details)</li><li><a href="https://code.google.com/p/memcached-session-manager/" target="_blank" rel="noopener">memcached-session-manager</a> (Tomcat high-availability sessions)</li><li><a href="http://code.google.com/p/mobility-rpc/" target="_blank" rel="noopener">Mobility-RPC</a> (RPC enabling distributed applications)</li><li><a href="https://github.com/romix/akka-kryo-serialization" target="_blank" rel="noopener">akka-kryo-serialization</a> (Kryo serializers for Akka)</li><li><a href="https://code.google.com/p/kryo/issues/detail?id=67" target="_blank" rel="noopener">Groupon</a></li><li><a href="http://www.jivesoftware.com/jivespace/blogs/jivespace/2010/07/29/the-jive-sbs-cache-redesign-part-3" target="_blank" rel="noopener">Jive</a></li><li><a href="https://code.google.com/p/destroyallhumans/" target="_blank" rel="noopener">DestroyAllHumans</a> (controls a <a href="http://www.youtube.com/watch?v=ZeZ3R38d3Cg" target="_blank" rel="noopener">robot</a>!)</li><li><a href="https://github.com/magro/kryo-serializers" target="_blank" rel="noopener">kryo-serializers</a> (additional serializers)</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;在2.22版本修正了许多之前反馈的问题，提高稳定性和性能。 它还引入了许多新的功能，最主要的是，它可以使用Unsafe的方式的直接读取和写入对象的内存。 这中绝对是最快的方式序列化方式，特别是在操作大型原始数组的时候。&lt;br&gt;Maven仓库中的JAR文件现在包含一个ObjectWeb的“阴影”版本ASM库，以避免与你项目中的ASM库出现兼容问题，从而导致你的应用程序粗线冲突。 这里不再需要一个单独的阴影jar了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;由于本篇文章是之前翻译的，最后迁移到该博客。在迁移过程中可能出现某些地方出错，并且由于水平有限，如果发生上面的情况，请大家发邮件告诉我，谢谢！&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Kryo&lt;/strong&gt;是一种快速，高效的对象图序列化的Java框架。 该项目的目标是速度，效率，以及一个易于使用的API。该项目对那些在任何时间，对象需要被持久化，无论是文件，数据库，或通过网络的项目都是适用的。Kryo还可以自动实现深浅的复制/克隆。 就是直接复制一个对象对象到例外一个对象，而不是对象-&amp;gt;字节-&amp;gt;对象。&lt;br&gt;本文档是Kryo的V2版本。如果V1版本感兴趣，见&lt;a href=&quot;https://github.com/EsotericSoftware/kryo/wiki/Documentation-for-Kryo-version-1.x&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;V1Documentation&lt;/a&gt;。&lt;br&gt;如果您打算使用Kryo进行网络通信，该&lt;a href=&quot;https://github.com/EsotericSoftware/kryonet&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KryoNet&lt;/a&gt;项目可能对你有用。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;内容&quot;&gt;&lt;a href=&quot;#内容&quot; class=&quot;headerlink&quot; title=&quot;内容&quot;&gt;&lt;/a&gt;内容&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Quickstart&lt;/li&gt;
&lt;li&gt;IO&lt;/li&gt;
&lt;li&gt;Unsafe-based IO&lt;/li&gt;
&lt;li&gt;Serializers&lt;/li&gt;
&lt;li&gt;Registration&lt;/li&gt;
&lt;li&gt;Default serializers&lt;/li&gt;
&lt;li&gt;FieldSerializer&lt;/li&gt;
&lt;li&gt;KryoSerializable&lt;/li&gt;
&lt;li&gt;Reading and writing&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;Object creation&lt;/li&gt;
&lt;li&gt;Copying/cloning&lt;/li&gt;
&lt;li&gt;Context&lt;/li&gt;
&lt;li&gt;Compression and encryption&lt;/li&gt;
&lt;li&gt;Chunked encoding&lt;/li&gt;
&lt;li&gt;Compatibility&lt;/li&gt;
&lt;li&gt;Interoperability&lt;/li&gt;
&lt;li&gt;Stack size&lt;/li&gt;
&lt;li&gt;Threading&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Integration with Maven&lt;/li&gt;
&lt;li&gt;Projects using Kryo&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="序列化" scheme="https://www.yangguo.info/tags/%E5%BA%8F%E5%88%97%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>论技术选型</title>
    <link href="https://www.yangguo.info/2013/11/03/%E8%AE%BA%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B/"/>
    <id>https://www.yangguo.info/2013/11/03/论技术选型/</id>
    <published>2013-11-03T15:58:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>人生就是一道选择题，对于码农来说更是如此。</p></blockquote><p>谁都不想做<strong>“起来一个大早，却赶了一个晚集”</strong>的人，如果人生的命运你不能掌握，至少在技术这条崎岖的道路上，我们也要努力避免这种悲剧的发生。</p><p>风起云涌的新技术，喧闹浮华背后的各大社区，各种榨取码农血汗钱的会议，当然还有各种很久不编码的人生导师，这些会对你每一次技术选型的抉择起到多大作用，我不得而知。我曾经流连于各大技术社区，听各路布道师的成功学，并将很多时髦的技术运用到我的工作中。我一度自我膨胀，认为自己是块材料，至少不是一块废材。陶醉在各种新技术的研究与布道上，直到有一天我离职的时候，我的大老板对我说<strong>“你们研究的那些非常好，但是跟业务有点脱钩”</strong>。我当时没有在意，因为谁会在意一个不懂技术的Boss的建议呢！但是有一天我在地铁上突然想起那句话，突然思绪就开始飞扬，所以就有了下面的文字。附带一句，本人坐地铁很少玩手机，一般我就做三件事情。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1.思考人生</span><br><span class="line">2.思考技术</span><br><span class="line">3.看美女</span><br></pre></td></tr></table></figure><p>正式由于第一件事情，让我对码农这个行业产生了新的认识。如果你不赞成我的观点，没有关系，因为我不企图改变任何人；如果你赞成，那我至少没有白忙活。</p><a id="more"></a><hr><h3 id="别高射炮打蚊子"><a href="#别高射炮打蚊子" class="headerlink" title="别高射炮打蚊子"></a>别高射炮打蚊子</h3><p>如果你是老板，5K的工程师能搞定的事情，你不会请个20K的人来搞。Hadoop在10年时还没有现在这🔥，至少没有到各家培训机构都在靠此赚钱的地步。11年有机会在公司上了一套Hadoop，直到我14年离开时，也就几台的规模，主要受限于公司的发展。在这2年多的时间里面我在hadoop上做了很多的小扩展，当时spring-data-hadoop项目还没有发布。</p><ol><li>支持自动打包直接上传到jobtracker，可以在任意java项目中运行。</li><li>使用snappy压缩。</li><li>扩展多种InputFormat/OutputFormat。</li><li>研究map端和reduce端join所代理的差异。</li><li>运维。</li><li>日志数据转化的自动化。</li><li>编写一些DSL的规则，自动生成job。</li><li>……</li></ol><p>这些事情，可能对于一些专职搞hadoop的人来说，这都不是个事。我就一个人瞎折腾，别人都在封装的底层类上进行逻辑封装。最后才发现我们3年下来的数据压缩之后才2T左右。而且我们根本就不会分析一年之前的数据。以前觉得这绝对是一个成功的项目，现在回想也许我用更低成本就可以做到比这还好。很火的大数据行业，究竟多少公司有大数据，我想大概一双手就能数过来吧。成本和收益对老板来说，才是最关心的事情，但是对于码农来说技术的成长才是最重要的。</p><hr><h3 id="是否有成功案例"><a href="#是否有成功案例" class="headerlink" title="是否有成功案例"></a>是否有成功案例</h3><p>12年底，由于公司的业务发展，需要一套实时推送的服务。之前是通过client端http定时轮询来拉去消息的，Boss觉得实时性不够高，要自己开发一套实时推送系统。虽然之前用OIO写过简单的socket server，但是从来没有开发过支撑百万级别的长连接的NIO Server，因为当时公司的APP装机量已经超过2000万。当然Android的活跃数并没有到百万级别，但是后台service一直连接着，所以对该推送Server的链接支撑数要求还是较高的。</p><p>查阅了网上很多资料，发现方案有如下几种：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 基于MQTT协议的消息中间件</span><br><span class="line">2. 基于MXPP协议</span><br><span class="line">3. 自己开私有协议</span><br></pre></td></tr></table></figure><p>当时网上有很多文章使用MQTT消息中间来实现推送服务，我当时就想到了Rabbitmq。由于Rabbitmq在公司内部已经稳定运行一年多了，并且有插件支持MQTT协议，加上是用Erlang开发的。我一想，这不是很简单嘛，在测试环境一测试，调了些Erlang虚拟机的参数，轻轻松松一台服务器就支撑到了30W链接的规模。Android端直接用IBM的MQTT Client，每一个id维护一个队列，有消息就往队列里面扔。第一版的方案就这样定了，在我们测试环境，表现得很理想。当时我们采用的是零时队列，就是如果链接断掉，队列就消失，这样内存就能回收。发送时去查询有哪些链接在上面，然后将消息通过routing key路由到指定的队列。我们当时知道这肯定会丢失消息，不过我们能够接受。我们最后放到移动网络一测试发现，我们的Rabbitmq中文件句柄一直增长，即使链接断掉了，当然我们在<a href="/2013/10/30/TCP参数调整/">TCP参数调整</a>方面有做了很多工作，但是都不能解决该问题，只好果断放弃该方案。我们花了大量的时间，在该方案上，结果发现该方案不行。当然最后我们自己基于Netty开发了一套自己私有协议的推送server，表现相当不错。XMPP太重，我们当时也排除了，当然不久之后，由于工作的变动，我有基于<a href="http://www.yangguo.info/2014/9/20/22.html">XMPP做了一套IM</a>，不过个人认为这个方案远不及私有协议来得爽。</p><p>最后说一句：<code>网上有很多不负责任的方案，自己根本没有做大量的测试就放出来，所以一定要有所甑别。</code></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;人生就是一道选择题，对于码农来说更是如此。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;谁都不想做&lt;strong&gt;“起来一个大早，却赶了一个晚集”&lt;/strong&gt;的人，如果人生的命运你不能掌握，至少在技术这条崎岖的道路上，我们也要努力避免这种悲剧的发生。&lt;/p&gt;
&lt;p&gt;风起云涌的新技术，喧闹浮华背后的各大社区，各种榨取码农血汗钱的会议，当然还有各种很久不编码的人生导师，这些会对你每一次技术选型的抉择起到多大作用，我不得而知。我曾经流连于各大技术社区，听各路布道师的成功学，并将很多时髦的技术运用到我的工作中。我一度自我膨胀，认为自己是块材料，至少不是一块废材。陶醉在各种新技术的研究与布道上，直到有一天我离职的时候，我的大老板对我说&lt;strong&gt;“你们研究的那些非常好，但是跟业务有点脱钩”&lt;/strong&gt;。我当时没有在意，因为谁会在意一个不懂技术的Boss的建议呢！但是有一天我在地铁上突然想起那句话，突然思绪就开始飞扬，所以就有了下面的文字。附带一句，本人坐地铁很少玩手机，一般我就做三件事情。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1.思考人生&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2.思考技术&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3.看美女&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;正式由于第一件事情，让我对码农这个行业产生了新的认识。如果你不赞成我的观点，没有关系，因为我不企图改变任何人；如果你赞成，那我至少没有白忙活。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="设计" scheme="https://www.yangguo.info/tags/%E8%AE%BE%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>TCP参数调整</title>
    <link href="https://www.yangguo.info/2013/10/30/TCP%E5%8F%82%E6%95%B0%E8%B0%83%E6%95%B4/"/>
    <id>https://www.yangguo.info/2013/10/30/TCP参数调整/</id>
    <published>2013-10-30T02:22:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近一直在进行手机推送服务器的研发，代码采用Java实现。netty作为一个优秀基于NIO的客户、服务器端编程框架，当然是首选的。server的研发必然是充满血与泪的，除了要熟悉Java的NIO，还得对tcp的封包、拆包，协议的设计、对象的序列化；压缩方式的选择等等之外，对Linux内核的调优是一件绝对比前面任何一个都要复杂。由于采用Java开发，由于Java的内存墙等诸多因素的影响，服务器支持的长连接我规划在一个server大概50W左右。可能对于一些C、C++服务器编程的码农来说这个链接数还真算不了什么，但是对于Java来说还是相当不错了。关于Java的内存墙方面的东西此处有一篇好文章推荐给大家，链接如下：<a href="http://ifeve.com/jvm-performance-optimization-java-scalability-5" target="_blank" rel="noopener">http://ifeve.com/jvm-performance-optimization-java-scalability-5</a>。既然一台服务器要支持50W的长连接 ，如果不对Linux内核进行调优，server的支撑的链接数和半链接的回收都会存在较大的问题。</p></blockquote><p>前面YY了很多了，OK那我们下面进入正题。调优，顾名思义那就是碰见问题才调优，下面就是我碰见的<strong>问题</strong>和<strong>解决方案</strong>：</p><p>1.要支持50W的长连接，TCP栈对内存的使用需要调大。tcp_rmem和tcp_wmem我们都采用缺省值8KB，那么一个tcp链接，需要占用的内存大概为16KB。<br>一个简单的计算如下：接近8.0GB TCP内存能容纳的连接数，约为  8000000KB/16KB  = 50万。所以下面我分别配置成3G，8G，12G，一定注意单位是page，大小一般为4KB。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt;net.ipv4.tcp_mem = 786432 2097152 3145728</span><br></pre></td></tr></table></figure><a id="more"></a><p>2.由于手机网络不是很稳定，会经常出现网络闪断的情况，server端如何快速回收链接。tcp是可靠的传输协议，链接的关闭也需要4次握手，可能很多人对链接建立的3次握手非常熟悉 ，但是对正常关闭的4次握手却不熟悉。下面我附带一张图:<br><img src="/images/tcp-close.jpg" alt="tcp-close"></p><p>此处我们思考的时候，把server和client的对调。当server端发现client端很久没有心跳，那我就得将该链接回收。由于Client端已经不可达，那server端链接会处在FIN-WAIT-1。这个时候该tcp链接已经是一个孤儿链接，也就是说它已经不属于任何一个进程。在不可达的情况下，它会默认发送9次，重试8次。由于该状态是非常占用资源的最大可占用64KB。所以我们得尽快让这个链接从FIN-WAIT-1中解放出来，所以设置如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt;net.ipv4.tcp_orphan_retries=1</span><br></pre></td></tr></table></figure><p>3.由于手机网络不是很稳定，可能会出现数据包的乱跳；最主要的是NAT环境如LVS，很多公司都用LVS做负载均衡，通常是前面一台LVS，后面多台后端服务器，以NAT方式构建，当请求到达LVS后，它修改地址数据后便转发给后端服务器，但不会修改时间戳数据，对于后端服务器来说，请求的源地址就是LVS的地址，加上端口会复用，所以从后端服务器的角度看，原本不同客户端的请求经过LVS的转发，就可能会被认为是同一个连接，加之不同客户端的时间可能不一致，所以就会出现时间戳错乱的现象，于是后面的数据包就被丢弃了，具体的表现通常是是客户端明明发送的SYN，但服务端就是不响应ACK。就会出现一部分手机就是链接不上。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">#是否启用时间戳选项，该选项会影响net.ipv4.tcp_tw_reuse，默认开启。一般我们为了安全我们不关闭该选项。</span><br><span class="line">net.ipv4.tcp_timestamps = 1</span><br><span class="line"></span><br><span class="line">#是否快速回收处于TIME_WAIT状态下得链接，默认关闭，最好别开启，除非有很懂的人建议。</span><br><span class="line">net.ipv4.tcp_tw_recycle = 0</span><br><span class="line"></span><br><span class="line">4.前面我们说了FIN-WAIT-1，那如何快速释放FIN-WAIT-2呢，虽然该状态没有FIN-WAIT-1那么耗资源。</span><br><span class="line">net.ipv4.tcp_fin_timeout=30</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">其余的都不是很复杂了，最复杂的就是上面几个。我贴一份全的给大家参考：</span><br><span class="line"></span><br><span class="line"># 开启TCP syncookies，防止DDOS攻击</span><br><span class="line">net.ipv4.tcp_syncookies = 1</span><br><span class="line">#syn报文（每个报文都需要排队）队列长度，超过该长度，请求就被丢弃，内存大于128M的默认为1024</span><br><span class="line">net.ipv4.tcp_max_syn_backlog = 65536</span><br><span class="line">#每个网络接口接收数据包的速率比内核处理这些包的速率快时，允许送到队列的数据包的最大数目</span><br><span class="line">net.core.netdev_max_backlog = 32768</span><br><span class="line">#定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数</span><br><span class="line">net.core.somaxconn = 32768  </span><br><span class="line">#是否启用时间戳选项，该选项会影响net.ipv4.tcp_tw_reuse，默认开启</span><br><span class="line">net.ipv4.tcp_timestamps = 1</span><br><span class="line">#是否快速回收处于TIME_WAIT状态下的socket，由于手机网络时间戳会出现乱跳，所以必须关闭，这个默认关闭。</span><br><span class="line">net.ipv4.tcp_tw_recycle = 0</span><br><span class="line">#被动接受tcp链接时，第二次握手发送SYNACKs的次数，默认为5，对应的时间大概为180秒，官方说法。</span><br><span class="line">net.ipv4.tcp_synack_retries = 3</span><br><span class="line">#跟上面刚好相反，是主动发起tcp链接，发送SYNs的次数，默认为5，对应的时间大概为180秒，官方时间。</span><br><span class="line">net.ipv4.tcp_syn_retries = 3</span><br><span class="line">#我们关闭了TIME_WAIT快速回收，我们通过tcp_tw_reuse和tcp_max_tw_buckets来控制TIME_WAIT避免吃光机器，该值默认180000.</span><br><span class="line">#如果服务器是作为客户端存在的，因为客户端连接受本地端口数限制，所以最好通过tcp_max_tw_buckets控制一下；如果服务器是作为服务端存在的，那么没有端口数的限制，只要情况允许，最好把tcp_max_tw_buckets设置大一些。纯粹就是防御dos攻击的，最好别认为降低该值。</span><br><span class="line">net.ipv4.tcp_max_tw_buckets=180000</span><br><span class="line">#开启处于TIME_WAIT态的socket重用，默认关闭。这个重用的是TIME_WAIT的端口，不是内存等，这个对客户端有意义。</span><br><span class="line">net.ipv4.tcp_tw_reuse=1</span><br><span class="line">#确定TCP栈如何使用内存，当大于上限是报文将丢弃。一般按照缺省值分配，上面的例子就是读写均为8KB，共16KB</span><br><span class="line">#1.6GB TCP内存能容纳的连接数，约为  1600MB/16KB = 100K = 10万</span><br><span class="line">#4.0GB TCP内存能容纳的连接数，约为  4000MB/16KB = 250K = 25万</span><br><span class="line">net.ipv4.tcp_mem = 786432 2097152 3145728</span><br><span class="line">#表示如果套接字由本端要求关闭，这个参数决定了它保持在FIN-WAIT-2状态的时间，2.2中默认180秒，之后默认为60秒  </span><br><span class="line">net.ipv4.tcp_fin_timeout=30</span><br><span class="line">#丢弃已经建立的tcp链接之前，需要多少次重试，默认15次，根据RTO的值，大概13-30分钟</span><br><span class="line">net.ipv4.tcp_retries2=5</span><br><span class="line">#放弃回应一个tcp连接请求之前，需要多少次重试，默认为3</span><br><span class="line">net.ipv4.tcp_retries1=3</span><br><span class="line">#收包速度大于内核处理包的速度时，输入队列最大报文数</span><br><span class="line">net.core.netdev_max_backlog =  32768</span><br><span class="line">#listen系统调用，最大的accept队列长度，超过该值时，后续请求被丢弃</span><br><span class="line">net.core.somaxconn=32768</span><br><span class="line">#针对孤立的socket（已经从进程上下文中删除，可是还有些清理工作没有完成），我们重试的最大次数。也就是server端close之后发[F.]的次数-1（0会重试一次），重负载服务器建议调小，默认为7。</span><br><span class="line">net.ipv4.tcp_orphan_retries=1</span><br></pre></td></tr></table></figure><hr><p><strong>PS：由于个人理解有限，如果不正确的请大家指正，以免误导他人，如果不明白原理，内核参数最好别动。</strong></p><hr><p>参考文章</p><p><a href="http://www.linuxinsight.com/" target="_blank" rel="noopener">http://www.linuxinsight.com/</a></p><p><a href="http://huoding.com/2012/01/19/142" target="_blank" rel="noopener">http://huoding.com/2012/01/19/142</a></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近一直在进行手机推送服务器的研发，代码采用Java实现。netty作为一个优秀基于NIO的客户、服务器端编程框架，当然是首选的。server的研发必然是充满血与泪的，除了要熟悉Java的NIO，还得对tcp的封包、拆包，协议的设计、对象的序列化；压缩方式的选择等等之外，对Linux内核的调优是一件绝对比前面任何一个都要复杂。由于采用Java开发，由于Java的内存墙等诸多因素的影响，服务器支持的长连接我规划在一个server大概50W左右。可能对于一些C、C++服务器编程的码农来说这个链接数还真算不了什么，但是对于Java来说还是相当不错了。关于Java的内存墙方面的东西此处有一篇好文章推荐给大家，链接如下：&lt;a href=&quot;http://ifeve.com/jvm-performance-optimization-java-scalability-5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://ifeve.com/jvm-performance-optimization-java-scalability-5&lt;/a&gt;。既然一台服务器要支持50W的长连接 ，如果不对Linux内核进行调优，server的支撑的链接数和半链接的回收都会存在较大的问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前面YY了很多了，OK那我们下面进入正题。调优，顾名思义那就是碰见问题才调优，下面就是我碰见的&lt;strong&gt;问题&lt;/strong&gt;和&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;1.要支持50W的长连接，TCP栈对内存的使用需要调大。tcp_rmem和tcp_wmem我们都采用缺省值8KB，那么一个tcp链接，需要占用的内存大概为16KB。&lt;br&gt;一个简单的计算如下：接近8.0GB TCP内存能容纳的连接数，约为  8000000KB/16KB  = 50万。所以下面我分别配置成3G，8G，12G，一定注意单位是page，大小一般为4KB。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&amp;gt;net.ipv4.tcp_mem = 786432 2097152 3145728&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="TCP" scheme="https://www.yangguo.info/tags/TCP/"/>
    
      <category term="Linux" scheme="https://www.yangguo.info/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>JVM最大线程数</title>
    <link href="https://www.yangguo.info/2013/06/17/JVM%E6%9C%80%E5%A4%A7%E7%BA%BF%E7%A8%8B%E6%95%B0/"/>
    <id>https://www.yangguo.info/2013/06/17/JVM最大线程数/</id>
    <published>2013-06-17T14:32:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近研发<strong>推送方案</strong>，需要大量线程来模拟手机客户端。在模拟手机客户端的时候，单个JVM线程数量始终卡在一万多一点，然后就报如下的错误<code>java.lang.OutOfMemoryError: unable to create new native thread</code>。我在网上找了很多资料，都是分析32位的，都是准备模拟几千个或者几万个水平。因为我是使用64位的linux,并且要模拟50万，发现很多都不是很详细，所以写了这篇文章。</p></blockquote><p>对于这个OOM，根本无需多言，网上一堆关于JVM线程数的计算公式，此处我不妨再次引用一次:<code>(MaxProcessMemory - JVMMemory – ReservedOsMemory) / (ThreadStackSize) = Number of threads</code>。MaxProcessMemory：进程最大的寻址空间，关于不同系统的进程默认的最大寻址空间，可以参考下图。</p><p><img src="/images/max-space-per-process.jpg" alt="进程的寻址空间"></p><a id="more"></a><h3 id="JVM层面"><a href="#JVM层面" class="headerlink" title="JVM层面"></a>JVM层面</h3><ul><li>JVMMemory:Heap + PermGen</li><li>ReservedOSMemory:Native heap，JNI</li></ul><p>通过上面的说明大概就能估算出线程数量，公式如下:</p><p><code>(MaxProcessMemory&lt;固定值&gt; – Xms&lt;初始化值，最小值&gt; – XX:PermSize&lt;初始化值，最小值&gt; – 100m&lt;估算值&gt;) / Xss = Number of threads&lt;最大值&gt;</code></p><p><em>-Xms，-Xmx，-Xss</em>，这三个变量就是JVM层面可以调节的。</p><hr><h3 id="系统层面"><a href="#系统层面" class="headerlink" title="系统层面:"></a>系统层面:</h3><h4 id="proc-sys-kernel-pid-max"><a href="#proc-sys-kernel-pid-max" class="headerlink" title="/proc/sys/kernel/pid_max"></a>/proc/sys/kernel/pid_max</h4><p><strong>开始我也觉得奇怪，为什么需要修改pid呢？pid不是进程id嘛，我只是创建线程，并不创建进程，pid的大小跟我好像没有关系。查阅资料才发现，同一个进程的多个线程的pid其实是不同的。</strong></p><h4 id="proc-sys-vm-max-map-count"><a href="#proc-sys-vm-max-map-count" class="headerlink" title="/proc/sys/vm/max_map_count"></a>/proc/sys/vm/max_map_count</h4><p><strong>max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。在进程的生命周期中，每当程序尝试在内存中映射文件，链接到共享内存段，或者分配堆空间的时候，这些区域将被创建。调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错，因为当进程达到了VMA上线但又只能释放少量的内存给其他的内核进程使用时，操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存，那么调低这个值可以帮助释放内存给内核用。具体的可以查看redhat的这篇文章:<a href="http://www.redhat.com/magazine/001nov04/features/vm/" target="_blank" rel="noopener">http://www.redhat.com/magazine/001nov04/features/vm/</a>，中文的话是这篇:<a href="http://www.oschina.net/translate/understanding-virtual-memory?print" target="_blank" rel="noopener">http://www.oschina.net/translate/understanding-virtual-memory?print</a></strong></p><p><strong>注意：</strong>ulimit -a中参数的大小</p><p>当然有人说下面这个两个需要修改，但是我测试下来，好像不用修改也行，反正我没有修改。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/proc/sys/kernel/thread-max</span><br><span class="line">/etc/security/limits.d/90-nproc.conf</span><br></pre></td></tr></table></figure><p>注意如果使用</p><ul><li>sysctl -w vm.max_map_count=200000</li><li>sysctl -p</li></ul><p>来修改，重启后值会改变，如果想永久改变需要采用如下形式。</p><ul><li>vm.max_map_count=200000直接写到/etc/sysctl.conf中</li><li>然后执行sysctl -p</li></ul><p>对于我的应用，此处还要设置一个socket端口的取值范围</p><ul><li>net.ipv4.ip_local_port_range = 1024 65535</li></ul><p>当然对于非socket应用是不需要设置该值的</p><blockquote><p><strong>最后说明</strong>:上面各个参数的设置，可以去查看linux方面的一些资料。至于值到底多大，得根据你机器的性能来确定。</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;最近研发&lt;strong&gt;推送方案&lt;/strong&gt;，需要大量线程来模拟手机客户端。在模拟手机客户端的时候，单个JVM线程数量始终卡在一万多一点，然后就报如下的错误&lt;code&gt;java.lang.OutOfMemoryError: unable to create new native thread&lt;/code&gt;。我在网上找了很多资料，都是分析32位的，都是准备模拟几千个或者几万个水平。因为我是使用64位的linux,并且要模拟50万，发现很多都不是很详细，所以写了这篇文章。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对于这个OOM，根本无需多言，网上一堆关于JVM线程数的计算公式，此处我不妨再次引用一次:&lt;code&gt;(MaxProcessMemory - JVMMemory – ReservedOsMemory) / (ThreadStackSize) = Number of threads&lt;/code&gt;。MaxProcessMemory：进程最大的寻址空间，关于不同系统的进程默认的最大寻址空间，可以参考下图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/max-space-per-process.jpg&quot; alt=&quot;进程的寻址空间&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="线程" scheme="https://www.yangguo.info/tags/%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>Hadoop节点动态管理</title>
    <link href="https://www.yangguo.info/2012/11/30/hadoop%E8%8A%82%E7%82%B9%E5%8A%A8%E6%80%81%E7%AE%A1%E7%90%86/"/>
    <id>https://www.yangguo.info/2012/11/30/hadoop节点动态管理/</id>
    <published>2012-11-30T15:42:29.000Z</published>
    <updated>2017-11-13T06:31:56.792Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>hadoop在线运行已经很长一段时间了，下面就是我在上下线datanode和tasktracker步骤。因为datanode节点不一定是tasktracker，即使datanode和tasktracker在同一节点，你也可能只上下线其中一个，所以我在配置dfs和mr的include和exclude的时候，分开配置。</p></blockquote><h3 id="namenode中hdfs-site-xml配置"><a href="#namenode中hdfs-site-xml配置" class="headerlink" title="namenode中hdfs-site.xml配置"></a>namenode中hdfs-site.xml配置</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">name</span>&gt;</span>dfs.hosts<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">value</span>&gt;</span>/ddmap/hadoop-1.0.4/conf/hdfs_include<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">name</span>&gt;</span>dfs.hosts.exclude<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">value</span>&gt;</span>/ddmap/hadoop-1.0.4/conf/hdfs_exclude<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><p>dfs.hosts所对应的文件中列出了所有可以连接到namenode的datanode，如果为空则所有的都可以连入。dfs.hosts.exclude所对应的文件中列出了禁止连接namenode的datanode节点。如果一个节点在这两个文件中都存在，则不允许连入。</p><a id="more"></a><hr><h3 id="下线datanode步骤"><a href="#下线datanode步骤" class="headerlink" title="下线datanode步骤"></a>下线datanode步骤</h3><ol><li>将要下线的机器加入到dfs.hosts.exclude指定的文件中（使用主机名，ip基本不靠谱），然后刷新配置hadoop dfsadmin -refreshNodes。</li><li>通过hadoop dfsadmin -report或者web界面，可以看到，该datanode状态转为Decommission In Progress。</li><li>当decommission进程完成数据移动，datanode状态会转变为Decommissioned，然后datanode会自动停止datnode进程。然后你可以看见dead nodes下多了一个你想要下线的节点。</li><li>然后删除include和exclude中该节点的hosts，重新刷新hadoop dfsadmin -refreshNodes。</li><li>最后别忘了删除slaves中该节点的配置，防止下次整个集群重启时，该节点不能通过namenode自动启动。</li></ol><p><strong>注意</strong>:当你下线一个datanode节点，有可能该节点长时间处于Decommission In Progress状态，一直不能转变为Decommissioned。请你用hadoop fsck /检查下是否有些块少于指定的块数，特别注意那些mapreduce的临时文件。将这些删除，并且从垃圾箱移除，该节点就可以顺利下线。这是我解决该问题的办法。</p><hr><h3 id="上线一个节点的步骤"><a href="#上线一个节点的步骤" class="headerlink" title="上线一个节点的步骤"></a>上线一个节点的步骤</h3><ol><li>保证将要上线的机器不存在于dfs.hosts.exclude所对应的的文件中，并且存在于dfs.hosts所对应的文件中。</li><li>在namenode上刷新配置：hadoop dfsadmin -refreshNodes。</li><li>在要上线的节点重启datanode,hadoop-daemon.sh start datanode。</li><li>通过hadoop dfsadmin -report或者web界面，可以看到，节点已经上线。</li><li>还是老话最后别忘了修改slaves。</li></ol><hr><h3 id="jobtracker中mapred-site-xml的配置"><a href="#jobtracker中mapred-site-xml的配置" class="headerlink" title="jobtracker中mapred-site.xml的配置"></a>jobtracker中mapred-site.xml的配置</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">name</span>&gt;</span>mapred.hosts<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">value</span>&gt;</span>/ddmap/hadoop-1.0.4/conf/mr_include<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">name</span>&gt;</span>mapred.hosts.exclude<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">value</span>&gt;</span>/ddmap/hadoop-1.0.4/conf/mr_exclude<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br></pre></td></tr></table></figure><p>tasktracker上下线基本同上。mapred.hosts中的文件是允许连接到jobtracker的机器，mapred.hosts.exclude则刚好相反，如果一台机器在两个配置中都存在，则不允许连接到jobtracker。</p><hr><h3 id="下线tasktracker步骤"><a href="#下线tasktracker步骤" class="headerlink" title="下线tasktracker步骤"></a>下线tasktracker步骤</h3><ol><li>将要下线的机器加入到mapred.hosts.exclude指定的文件中（使用主机名，ip基本不靠谱）。</li><li>刷新配置：hadoop mradmin -refreshNodes，你可以看见web界面Excluded Nodes中多出了一个节点，该节点就是你要下线的节点，最后到你要下线的节点上运行hadoop-daemon.sh stop tasktracker。</li><li>最后将mr_exclude和mr_include中该节点的hosts删除，并在jobtracker上再次刷新该配置，hadoop mradmin -refreshNodes。</li><li>再次提醒别忘了修改slaves。</li></ol><p>tasktracker上线基本上和datnode上线原理和步骤都差不多，此处就不说了。<br>tasktracker上下线问题不是很大，最多就是任务失败，我想这个对于很多人来说都是可以忍受的，但是datanode上下线需要注意，因为datanode上下线如果不得当，可能导致数据丢失，特别是同事操作2个以上的节点。如果集群不是非常大，最好的每次下线一至两台，通过检查发现数据没有问题的时候再下线别的。这样虽然麻烦，但是是最安全的。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;hadoop在线运行已经很长一段时间了，下面就是我在上下线datanode和tasktracker步骤。因为datanode节点不一定是tasktracker，即使datanode和tasktracker在同一节点，你也可能只上下线其中一个，所以我在配置dfs和mr的include和exclude的时候，分开配置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;namenode中hdfs-site-xml配置&quot;&gt;&lt;a href=&quot;#namenode中hdfs-site-xml配置&quot; class=&quot;headerlink&quot; title=&quot;namenode中hdfs-site.xml配置&quot;&gt;&lt;/a&gt;namenode中hdfs-site.xml配置&lt;/h3&gt;&lt;figure class=&quot;highlight xml&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;property&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;/span&gt;dfs.hosts&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;value&lt;/span&gt;&amp;gt;&lt;/span&gt;/ddmap/hadoop-1.0.4/conf/hdfs_include&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;value&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;property&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;property&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;/span&gt;dfs.hosts.exclude&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;name&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt; &lt;span class=&quot;tag&quot;&gt;&amp;lt;&lt;span class=&quot;name&quot;&gt;value&lt;/span&gt;&amp;gt;&lt;/span&gt;/ddmap/hadoop-1.0.4/conf/hdfs_exclude&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;value&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;/&lt;span class=&quot;name&quot;&gt;property&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;dfs.hosts所对应的文件中列出了所有可以连接到namenode的datanode，如果为空则所有的都可以连入。dfs.hosts.exclude所对应的文件中列出了禁止连接namenode的datanode节点。如果一个节点在这两个文件中都存在，则不允许连入。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="BigData" scheme="https://www.yangguo.info/tags/BigData/"/>
    
  </entry>
  
  <entry>
    <title>MongoDB磁盘整理</title>
    <link href="https://www.yangguo.info/2012/10/20/mongodb%E7%A3%81%E7%9B%98%E6%95%B4%E7%90%86/"/>
    <id>https://www.yangguo.info/2012/10/20/mongodb磁盘整理/</id>
    <published>2012-10-20T13:22:30.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>虽然<strong>mongodb</strong>作为一个款明星级的NoSql数据库，但是它的磁盘回收却让很多人较为头痛。</p></blockquote><h3 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h3><ol><li>下面的方案只适用于<strong>replica sets</strong>模式的mongodb集群。</li><li>我们线上的mongodb版本为2.0.0，但是也适合于其他的版本。</li></ol><hr><h3 id="自动同步方案"><a href="#自动同步方案" class="headerlink" title="自动同步方案"></a>自动同步方案</h3><p>依赖于mongodb的同步方案，即不采用fastsync方式。这种方案比较简单，但是只能针对数据量和新增数据量不多的情况。因为如果你的数据量太大而新增数据又太多，可能就会出现同步时间太长，而oplog已经把你还没有同步的日志给覆盖了，从而导致集群数据不一致的情况。具体操作步骤如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">1.首先在primary将需要整理的secondary下线：rs.remove(“172.16.1.153:10000”)。</span><br><span class="line">2.将下线的secondary关闭，并且删除所有数据文件。</span><br><span class="line">3.将配置文件中fastsync=true修改为false，重启该节点。</span><br><span class="line">4.在primary将该节点加入集群，等待数据同步：rs.add(&#123;_id:需根据情况设定,host:“172.16.1.153:10000”&#125;)。</span><br><span class="line">5.一定要将fastsync修改成true，防止以后重启又全量同步，除非你真的就是这样设计的。</span><br><span class="line">6.等secondary同步完成之后，会自动上线，只需要通过命令观察该节点状态。</span><br><span class="line">7.该节点上线之后，进行数据对比，如果数据没有问题，就对别的secondary节点进行操作。</span><br><span class="line">8.等各个secondary节点全搞定之后，将primary降级，让secondary节点升级为primary 。</span><br><span class="line">9.primary降级成secondary之后，重复1-6步骤。</span><br><span class="line">10.如果你原来的primary上线之后，你还是想让它成为primary，那么就进行升级操作，升级操作跟降级操作一样，只是设置的值不一样而已。</span><br></pre></td></tr></table></figure><a id="more"></a><hr><h3 id="特别说明"><a href="#特别说明" class="headerlink" title="特别说明"></a>特别说明</h3><ol><li>检查数据是否跟主节点一致非常重要，因为如果数据不一致，肯定会造成数据丢失情况。</li><li>降级操作图见下图：</li></ol><p><img src="/images/mongodb.png" alt="Mou icon"></p><hr><h3 id="RepairDatabase方案"><a href="#RepairDatabase方案" class="headerlink" title="RepairDatabase方案"></a>RepairDatabase方案</h3><p>由于repairDatabase命令，会锁住整个库，并且在2.0.0不能运行在secondary节点，但是在2.6之后就可以在secondary节点运行了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. 先将secondary节点下线</span><br><span class="line">2. 将下线的节点通过standalone的模式运行。</span><br><span class="line">3. 然后运行mongod --repair或者mongod --repair --repairpath /opt/vol2/data命令来compacts all collections。</span><br><span class="line">4. 等待紧缩完了之后，再将该节点作为一个secondary添加到集群。</span><br><span class="line">5. 等待通过oplog将数据同步完成之后，进行数据检查。</span><br><span class="line">6. 剩余的步骤可以参考自动同步方案的做法。</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. RepairDatabase和自动同步方案的效率，我没有比较过，并且我在线都使用的是自动同步方案，不是因为第二种方案不好，因为我刚开始就选择了该方案，并且没有出过问题，为了线上数据的安全，因此一直采用此种方案。</span><br><span class="line">2. 2.6之后，是否可以不将secondary下线，直接在线压缩，没有测试过，如果能这样做，那就非常爽。</span><br><span class="line">3. 最后提醒大家，这种一定要经过大量测试，切实可行，再操作，否者数据丢了可以不是小事。</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;虽然&lt;strong&gt;mongodb&lt;/strong&gt;作为一个款明星级的NoSql数据库，但是它的磁盘回收却让很多人较为头痛。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;说明&quot;&gt;&lt;a href=&quot;#说明&quot; class=&quot;headerlink&quot; title=&quot;说明&quot;&gt;&lt;/a&gt;说明&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;下面的方案只适用于&lt;strong&gt;replica sets&lt;/strong&gt;模式的mongodb集群。&lt;/li&gt;
&lt;li&gt;我们线上的mongodb版本为2.0.0，但是也适合于其他的版本。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;自动同步方案&quot;&gt;&lt;a href=&quot;#自动同步方案&quot; class=&quot;headerlink&quot; title=&quot;自动同步方案&quot;&gt;&lt;/a&gt;自动同步方案&lt;/h3&gt;&lt;p&gt;依赖于mongodb的同步方案，即不采用fastsync方式。这种方案比较简单，但是只能针对数据量和新增数据量不多的情况。因为如果你的数据量太大而新增数据又太多，可能就会出现同步时间太长，而oplog已经把你还没有同步的日志给覆盖了，从而导致集群数据不一致的情况。具体操作步骤如下：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1.首先在primary将需要整理的secondary下线：rs.remove(“172.16.1.153:10000”)。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2.将下线的secondary关闭，并且删除所有数据文件。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3.将配置文件中fastsync=true修改为false，重启该节点。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4.在primary将该节点加入集群，等待数据同步：rs.add(&amp;#123;_id:需根据情况设定,host:“172.16.1.153:10000”&amp;#125;)。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5.一定要将fastsync修改成true，防止以后重启又全量同步，除非你真的就是这样设计的。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6.等secondary同步完成之后，会自动上线，只需要通过命令观察该节点状态。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7.该节点上线之后，进行数据对比，如果数据没有问题，就对别的secondary节点进行操作。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8.等各个secondary节点全搞定之后，将primary降级，让secondary节点升级为primary 。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9.primary降级成secondary之后，重复1-6步骤。&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10.如果你原来的primary上线之后，你还是想让它成为primary，那么就进行升级操作，升级操作跟降级操作一样，只是设置的值不一样而已。&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="数据库" scheme="https://www.yangguo.info/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>MongoDB并发控制</title>
    <link href="https://www.yangguo.info/2012/05/19/mongodb%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6/"/>
    <id>https://www.yangguo.info/2012/05/19/mongodb并发控制/</id>
    <published>2012-05-19T03:02:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>MongoDB在我们的生产环境中已经大规模的使用，它的性能与稳定已经得到的充分的验证，稳定在线的时间已经有一年多了。在这个过程中的确给我们带来了很多性能上的优势，虽然它不像关系型数据那样有方便的join查询，但就目前我们的应用场景这些缺点（暂且把它当做缺点吧）都是可以接受的。最近在思考了下nosql数据库并发控制方面的问题，在此记录一下。</p></blockquote><p>数据库的<strong>并发控制</strong>机制不外乎就两种情况：</p><ol><li><a href="http://zh.wikipedia.org/wiki/%E6%82%B2%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6" target="_blank" rel="noopener">悲观锁</a>：假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作。</li><li><a href="http://zh.wikipedia.org/wiki/%E4%B9%90%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6" target="_blank" rel="noopener">乐观锁</a>：假设不会发生并发冲突，只在提交操作时检查是否违反数据完整性，乐观锁不能解决脏读和多读的问题。<br>悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的，因此在悲观锁的环境中，在你开始改变此对象之前就将该对象锁住，并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁，加锁的时间可能会很长，这样可能会长时间的限制其他用户的访问，也就是说悲观锁的并发访问性不好。<br>乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的，因此乐观锁直到你准备提交所作的更改时才将对象锁住，当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短，乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象，那么当他完成了自己的更改进行提交时，数据库就会发现该对象已经变化了，这样，第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中，会增加并发用户读取对象的次数。</li></ol><a id="more"></a><p>MongoDB不支持事务，也就没有数据库级别的悲观锁了。那么我们的并发控制只能依赖乐观锁，乐观锁非常适合我的应用场景，并且性能更高。刚才说的是数据库级别的并发控制，当然如果说到程序级别并发控制机制，同样是悲观锁和乐观锁。我们的经常用的lock就是一种悲观锁，不论是<code>java</code>还是<code>.net</code>。那乐观锁呢？<a href="http://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E4%BA%8B%E5%8A%A1%E5%86%85%E5%AD%98" target="_blank" rel="noopener">软件事务内存</a>，<a href="http://zh.wikipedia.org/wiki/Clojure" target="_blank" rel="noopener">Clojure</a>和<a href="http://zh.wikipedia.org/wiki/Scala" target="_blank" rel="noopener">Scala</a>言语的内存管理都大量使用了乐观锁，因此它们天生就是支持并发编程的。</p><hr><p>如果要在普通的关系型数据库里实现乐观并发控制，我们一般需要为其加上一个额外的Version字段，它是整型，也可能是个时间戳。在更新某条记录时，我们将这个字段的“旧值”作为UPDATE语句的条件之一，同时这个字段也会写入新的值。如果这次更新影响了某条记录，那么表示更新成功，反之则表示这条记录已经被删除，或是在“读取”和“提交”之间遇到了其他提交操作。在SQL Server中存在一个Timestamp类型，这个类型的字段会在记录修改时自动更新。</p><p>要在MongoDB实现乐观锁，方式差不多，只是update完了之后，不会返回修改的数据条数，得还要自己去查询一下是否修改成功。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt;db.log.update(&#123;<span class="string">"uuid"</span>:<span class="string">"1"</span>,version:1&#125;,&#123;<span class="variable">$set</span>:&#123;<span class="string">"enddate"</span>:<span class="string">"2012-5-19 17:17:10"</span>,version:2&#125;&#125;)</span><br><span class="line">&gt; db.<span class="variable">$cmd</span>.findOne(&#123;getlasterror:1&#125;) &#123;   <span class="string">"updatedExisting"</span> : <span class="literal">true</span>,     <span class="string">"n"</span> : 1,        <span class="string">"connectionId"</span> : 1, <span class="string">"err"</span> : null,        <span class="string">"ok"</span> : 1 &#125;</span><br><span class="line">&gt;db.log.update(&#123;<span class="string">"uuid"</span>:<span class="string">"1"</span>,version:1&#125;,&#123;<span class="variable">$set</span>:&#123;<span class="string">"enddate"</span>:<span class="string">"2012-5-19 17:17:10"</span>,version:2&#125;&#125;)</span><br><span class="line">&gt; db.<span class="variable">$cmd</span>.findOne(&#123;getlasterror:1&#125;) &#123;   <span class="string">"updatedExisting"</span> : <span class="literal">false</span>,     <span class="string">"n"</span> : 0,        <span class="string">"connectionId"</span> : 1, <span class="string">"err"</span> : null,        <span class="string">"ok"</span> : 1 &#125;</span><br></pre></td></tr></table></figure><p>在update语句后面跟上一句db.$cmd查询，如果它返回updatedExisting为true，则表示更新成功了。当然如果使用java驱动的话，可以使用dbCollection.update和dbCollection.getStats()，便可以更新并且返回状态信息。但是db.$cmd查询的结果是否准确呢？如果在update语句和db.$cmd查询之间，如果另外一个连接恰好也执行了一次update操作，那么db.$cmd返回的是哪次更新的结果？通过查询官方资料，db.$cmd查询是与连接相关，这便不会有问题了。不过值得注意的是，驱动程序是“自动管理连接”的，也就是说当update()完成之后getstats()有可能使用的不是同一个链接了，这个时候db.$cmd返回的状态信息就不准确。所以如果采用上诉方式你要确保自己两次获得的是同一个链接。如果你想一直使用同一个连接，可以用下边这种方式：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DB db...;</span><br><span class="line">db.requestStart();</span><br><span class="line">code....</span><br><span class="line">db.requestDone();</span><br></pre></td></tr></table></figure><p>但是如果最后db.requestDone()没有被调用，该连接不会被交还给线程池，所以，一定要在finally块中调用db.requestDone()。</p><p>由于我使用spring来管理mongo驱动，我不喜欢上面那种保持一个链接的方式，所以我用<a href="http://docs.mongodb.org/manual/reference/command/findAndModify/" target="_blank" rel="noopener">findAndModify</a>来更新数据，<code>当然更新条件自然同样需要包括版本号。如果更新成功，那么findAndModify命令则会返回“更新前”的数据，否则则返回空文档</code>。这种使用数据库命令行的方式就可以避免保持同一个链接的要求。该方式也是我推荐的方式，官方文档已经说的很清楚了。</p><blockquote><p>This command can be used to atomically modify a document (at most one) and return it. Note that, by default, the document returned will not include the modifications made on the update.</p></blockquote><p>以上便是mongodb并发控制乐观锁的实现方式。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;MongoDB在我们的生产环境中已经大规模的使用，它的性能与稳定已经得到的充分的验证，稳定在线的时间已经有一年多了。在这个过程中的确给我们带来了很多性能上的优势，虽然它不像关系型数据那样有方便的join查询，但就目前我们的应用场景这些缺点（暂且把它当做缺点吧）都是可以接受的。最近在思考了下nosql数据库并发控制方面的问题，在此记录一下。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;数据库的&lt;strong&gt;并发控制&lt;/strong&gt;机制不外乎就两种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://zh.wikipedia.org/wiki/%E6%82%B2%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;悲观锁&lt;/a&gt;：假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://zh.wikipedia.org/wiki/%E4%B9%90%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;乐观锁&lt;/a&gt;：假设不会发生并发冲突，只在提交操作时检查是否违反数据完整性，乐观锁不能解决脏读和多读的问题。&lt;br&gt;悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的，因此在悲观锁的环境中，在你开始改变此对象之前就将该对象锁住，并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁，加锁的时间可能会很长，这样可能会长时间的限制其他用户的访问，也就是说悲观锁的并发访问性不好。&lt;br&gt;乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的，因此乐观锁直到你准备提交所作的更改时才将对象锁住，当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短，乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象，那么当他完成了自己的更改进行提交时，数据库就会发现该对象已经变化了，这样，第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中，会增加并发用户读取对象的次数。&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Nosql" scheme="https://www.yangguo.info/tags/Nosql/"/>
    
      <category term="数据库" scheme="https://www.yangguo.info/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>集中式配置中心设计</title>
    <link href="https://www.yangguo.info/2011/06/11/%E9%9B%86%E4%B8%AD%E5%BC%8F%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E8%AE%BE%E8%AE%A1/"/>
    <id>https://www.yangguo.info/2011/06/11/集中式配置中心设计/</id>
    <published>2011-06-11T15:01:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>随着公司的发展，团队成员和项目数越来越多，系统架构不可避免的要向系统安全和开发运维的成本等方面倾斜。毕竟过了小米加步枪的高速发展阶段，我们必须要鸟枪换炮，将系统的质量提高一个层级。</p></blockquote><h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><p>绝大多数项目都离不开包含私密信息的配置文件，这些配置信息之前是分散在数以百计的项目中。不但管理起来麻烦，而且配置信息都放在项目中不可避免的存在安全隐患。我们经过分析研究，发现很多项目的配置都存在共性，如数据库的配置，日志的输出，ZK服务器的地址等等。虽然在maven中有阿里的<a href="http://openwebx.org/docs/autoconfig.html" target="_blank" rel="noopener"><strong>auto-config</strong></a>插件，在安全性方面有所提升。但是由于该插件只能工作在maven，不能用于gradle等构建工具，并且每个项目还要按照要求编写模板。所以我们需要代码和配置完全分离，不依赖于任何的构建工具，项目代码中只有代码逻辑。</p><p>由于我们有些项目中的配置还需要实时的变动，并同步到所有机器，写死在配置文件中，那么服务就必须要重新启动，当然通过JMX或者通过服务调用来修改都行，但是如果服务集群较多，就会存在不小的工作量。所以我们需要希望我们的配置有发布订阅的功能。</p><p>最后所有的配置信息集中存储，统一管理，各个项目中不再需要配置文件。开发人员只需要关心业务，配置全交给运维人员，并且如果配置如果出现异常，可以快速回滚。</p><a id="more"></a><h3 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h3><p>需求明确了之后，我们将所有的项目进行了梳理，所有的配置信息类型可以归纳为下图：</p><p><img src="/images/config-type.png" alt="config-type"></p><p>从上图，我们可以看出其实我们的配置就是一个4层的文件夹。配置的存储，<a href="http://zookeeper.apache.org/" target="_blank" rel="noopener">ZooKeeper</a>当然是不二的选择。原因如下：</p><ol><li>ZK本来就是我们基础设施中不可获取的一环。</li><li>配置信息的结构和ZK的功能设计完全契合。</li><li>事件通知ZK天生就支持。</li><li>ZK的高可用。</li></ol><p>在做架构时，我们必须要保证系统能够平滑升级，将老系统升级的风险降到最低。我们发现90%的老系统都使用了spring框架，并且这些系统中90%的配置都是由spring加载。所以代码层面的切入点就非常明确了，分为如下两步：</p><ol><li>包装一个ZK的工具类ZKUtils，实现读取ZK的配置和订阅文件的变化。</li><li>扩展<code>org.springframework.beans.factory.config.PropertyPlaceholderConfigurer</code>，让其能够从ZK读取配置。</li></ol><h3 id="技术细节"><a href="#技术细节" class="headerlink" title="技术细节"></a>技术细节</h3><p>在技术的实现上，我们遵循Convention Over Configuration(约定优于配置)，下面以一个名为Test的项目作为案例来说明。</p><table><thead><tr><th>配置的级别</th><th>路径</th></tr></thead><tbody><tr><td>Global</td><td>/Global/Properties/Test</td></tr><tr><td>Project</td><td>/Global/Project/Properties/Test</td></tr><tr><td>Server</td><td>/Global/Project/Server/Properties/Test</td></tr><tr><td>Process</td><td>/Global/Project/Server/{进程标识如端口号}/Properties/Test</td></tr></tbody></table><p>项目名称也约定配置在classpath下的一个文件中，该项目名称会用在所有的内部框架中，如日志的输出文件名，配置文件的路径，监控中心的名称等等。</p><p>ZKUtils的实现就不说了，直接使用<strong><a href="http://curator.apache.org/" target="_blank" rel="noopener">Curator</a></strong>稍微封装下，就能够满足需求。</p><p><strong>PropertyPlaceholderConfigurer</strong>的实现类就直接贴代码了，都很简单。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:11/9/23</span></span><br><span class="line"><span class="comment"> * Time:下午10:34</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestPropertyPlaceholderConfigurer</span> <span class="keyword">extends</span> <span class="title">PropertyPlaceholderConfigurer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * spring需要加载的properties文件路径列表</span></span><br><span class="line"><span class="comment">     * &lt;br/&gt;</span></span><br><span class="line"><span class="comment">     * 文件路径名约定为"&#123;scope:path&#125;"</span></span><br><span class="line"><span class="comment">     * &lt;br/&gt;</span></span><br><span class="line"><span class="comment">     * 如：项目Test中配置"Global:properties/redis.properties"代表读取"/Global/Properties/Test/properties/redis.properties"文件。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setTestProperties</span><span class="params">(List&lt;String&gt; testProperties)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">null</span> == testProperties || testProperties.size() &lt;= <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"文件路径名不能为空"</span>);</span><br><span class="line"></span><br><span class="line">        List&lt;Properties&gt; propertiesArray = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (String value : testProperties) &#123;</span><br><span class="line">            String[] strs = value.split(<span class="string">":"</span>);</span><br><span class="line">            <span class="keyword">if</span> (strs.length != <span class="number">2</span>)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"文件路径必须是&#123;scope:path&#125;的形式。如：项目Test中配置\"Global:properties/redis.properties\"代表读取\"/Global/Properties/Test/properties/redis.properties\"文件"</span>);</span><br><span class="line"></span><br><span class="line">            propertiesArray.add(ZKUtils.getConfProperties(strs[<span class="number">0</span>], strs[<span class="number">1</span>]));</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">        setPropertiesArray(propertiesArray.toArray(<span class="keyword">new</span> Properties[propertiesArray.size()]));</span><br><span class="line">        setIgnoreUnresolvablePlaceholders(<span class="keyword">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置的使用如下</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="php"><span class="meta">&lt;?</span>xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span><span class="meta">?&gt;</span></span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag"><span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag"><span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span></span></span><br><span class="line"><span class="tag"><span class="attr">class</span>=<span class="string">"info.yangguo.TestPropertyPlaceholderConfigurer"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"testProperties"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">list</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">value</span>&gt;</span>Global:properties/redis.properties<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">value</span>&gt;</span>Global:properties/mysql.properties<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">list</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"info.yangguo.redis"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"info.yangguo.dao"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上面就是我在设计这个集中式配置中心的时候的一些想法，当然代码的具体实现比这个复杂多了，我只是捡最重要的说，是想把一个比较复杂的东西简单化。其实里面还有很多的容错机制，比如ZK集群宕机怎么办，是否可以读取本地的缓存等等。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;随着公司的发展，团队成员和项目数越来越多，系统架构不可避免的要向系统安全和开发运维的成本等方面倾斜。毕竟过了小米加步枪的高速发展阶段，我们必须要鸟枪换炮，将系统的质量提高一个层级。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;需求&quot;&gt;&lt;a href=&quot;#需求&quot; class=&quot;headerlink&quot; title=&quot;需求&quot;&gt;&lt;/a&gt;需求&lt;/h3&gt;&lt;p&gt;绝大多数项目都离不开包含私密信息的配置文件，这些配置信息之前是分散在数以百计的项目中。不但管理起来麻烦，而且配置信息都放在项目中不可避免的存在安全隐患。我们经过分析研究，发现很多项目的配置都存在共性，如数据库的配置，日志的输出，ZK服务器的地址等等。虽然在maven中有阿里的&lt;a href=&quot;http://openwebx.org/docs/autoconfig.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;auto-config&lt;/strong&gt;&lt;/a&gt;插件，在安全性方面有所提升。但是由于该插件只能工作在maven，不能用于gradle等构建工具，并且每个项目还要按照要求编写模板。所以我们需要代码和配置完全分离，不依赖于任何的构建工具，项目代码中只有代码逻辑。&lt;/p&gt;
&lt;p&gt;由于我们有些项目中的配置还需要实时的变动，并同步到所有机器，写死在配置文件中，那么服务就必须要重新启动，当然通过JMX或者通过服务调用来修改都行，但是如果服务集群较多，就会存在不小的工作量。所以我们需要希望我们的配置有发布订阅的功能。&lt;/p&gt;
&lt;p&gt;最后所有的配置信息集中存储，统一管理，各个项目中不再需要配置文件。开发人员只需要关心业务，配置全交给运维人员，并且如果配置如果出现异常，可以快速回滚。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="设计" scheme="https://www.yangguo.info/tags/%E8%AE%BE%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>详解Java Thread中的原始方法</title>
    <link href="https://www.yangguo.info/2011/03/09/%E8%AF%A6%E8%A7%A3Java%20Thread%E4%B8%AD%E7%9A%84%E5%8E%9F%E5%A7%8B%E6%96%B9%E6%B3%95/"/>
    <id>https://www.yangguo.info/2011/03/09/详解Java Thread中的原始方法/</id>
    <published>2011-03-09T14:56:29.000Z</published>
    <updated>2017-09-23T02:15:45.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Thread中大量的方法已经被抛弃，很多人可能都知道它们线程不安全，本篇文章就是要详细的介绍这些不被推荐的方法背后隐藏的诸多陷阱。</p></blockquote><h3 id="Thread-stop"><a href="#Thread-stop" class="headerlink" title="Thread.stop"></a>Thread.stop</h3><p>当一个正在运行的线程，被执行stop时，该线程会抛出一个<strong>ThreadDeath</strong>实例。如果此时该线程已经进入<a href="https://zh.wikipedia.org/zh-cn/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96\" target="_blank" rel="noopener">管程</a>)中，就可能使受管程保护的对象出现不确定的状态，从而导致使用该对象的别的线程出现问题。这样的对象，我们称之为<strong>损坏的对象</strong>。当线程操作包含损坏对象，任意的行为都会影响结果，而这些行为又是琢磨不定的。<strong>ThreadDeath</strong>不像别的未检查异常，它会悄悄的杀死线程，不会有任何警告，从而导致程序腐化。代码腐化可能会在任何时候导致实际损害发生，甚至在未来几小时或者几天。</p><a id="more"></a><p>为了形象的说明代码腐化所带来的影响，我们可以详解下面这段代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by IntelliJ IDEA</span></span><br><span class="line"><span class="comment"> * User:杨果</span></span><br><span class="line"><span class="comment"> * Date:11/3/9</span></span><br><span class="line"><span class="comment"> * Time:下午10:05</span></span><br><span class="line"><span class="comment"> * &lt;p/&gt;</span></span><br><span class="line"><span class="comment"> * Description:</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadDeathTest</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> resource = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">monitor</span><span class="params">(String threadName)</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"*****************"</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            System.out.println(threadName + <span class="string">"进入管程"</span>);</span><br><span class="line">            System.out.println(threadName + <span class="string">"开始操作互斥资源"</span>);</span><br><span class="line">            resource += <span class="number">1</span>;</span><br><span class="line">            Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">            resource -= <span class="number">1</span>;</span><br><span class="line">            System.out.println(threadName + <span class="string">"互斥资源操作完成"</span>);</span><br><span class="line">            System.out.println(threadName + <span class="string">"退出管程"</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">            System.out.println(<span class="string">"捕获非检查异常:"</span> + ex);</span><br><span class="line">            <span class="comment">//ex.printStackTrace();</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread thread1 = <span class="keyword">new</span> Thread() &#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    monitor(<span class="string">"Thread1"</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;;</span><br><span class="line">            thread1.start();</span><br><span class="line">            Thread thread2 = <span class="keyword">new</span> Thread() &#123;</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    monitor(<span class="string">"Thread2"</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;;</span><br><span class="line">            thread2.start();</span><br><span class="line">            <span class="comment">//给充足的时间，让线程启动</span></span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">            thread1.stop();</span><br><span class="line">            thread2.join();</span><br><span class="line">            System.out.println(<span class="string">"------------------"</span>);</span><br><span class="line">            System.out.println(<span class="string">"互斥资源期待值为："</span> + <span class="number">0</span>);</span><br><span class="line">            System.out.println(<span class="string">"互斥资源实际值为："</span> + resource);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">            System.out.println(<span class="string">"Caught in main: "</span> + t);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段程序的输出的结果为</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">*****************</span><br><span class="line">Thread1进入管程</span><br><span class="line">Thread1开始操作互斥资源</span><br><span class="line">捕获非检查异常:java.lang.ThreadDeath</span><br><span class="line">*****************</span><br><span class="line">Thread2进入管程</span><br><span class="line">Thread2开始操作互斥资源</span><br><span class="line">Thread2互斥资源操作完成</span><br><span class="line">Thread2退出管程</span><br><span class="line">------------------</span><br><span class="line">互斥资源期待值为：0</span><br><span class="line">互斥资源实际值为：1</span><br></pre></td></tr></table></figure><p>代码中我们用一个同步方法<strong>monitor</strong>（管程）来保护<strong>resource</strong>，确保它会被互斥的使用。<strong>Thread1</strong>和<strong>Thread2</strong>都需要调用同步方法，来操作<strong>resource</strong>。<strong>Thread1</strong>先进入<strong>monitor</strong>，<strong>Thread2</strong>在等待互斥锁，当<strong>Thread1</strong>被调用了<strong>stop</strong>时还处于同步块，还没有来的急将<strong>resource</strong>处理完，便由于抛出<strong>TheadDeath</strong>释放了互斥锁，<strong>Thread2</strong>获得锁进入管程，便得到上面的结果。那这个代码就腐化了，这时产生的结果就会存在问题的。</p><p>那既然<strong>ThreadDeath</strong>会导致代码腐化，那我们是否可以捕获它，然后修复已经<strong>损坏的对象</strong>呢？</p><p>当然，理论上这是可行的，但是这会给编写正确的多线程代码增加极大的难度。该任务基本上是不可能成功的，原因有如下两点：</p><ol><li>一个线程可以在任何地方抛出一个ThreadDeath异常。基于这点，所有的同步方法和块必须详细的设计。</li><li>一个线程在<strong>catch</strong>或者<strong>finally</strong>中清理<strong>ThreadDeath</strong>时，可能会抛出第二个<strong>ThreadDeath</strong>。清理工作要一直重复，直到清理成功。这个确保代码是非常复杂的。</li></ol><p>总之，该方法是不适用的。</p><p>除了上面提供的所有问题，<strong>Thread.stop(Throwable)</strong>方法可能会被用来生成目标线程未准备处理的异常（如果不是因为该方法，线程不太可能抛出包括已检查异常在内的异常）。下面例子中方法的行为等同于<strong>throw</strong>操作，但是它绕开了编译器保证每个方法调用者都能知道方法会抛出的已检查异常。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sneakyThrow</span><span class="params">(Throwable t)</span> </span>&#123;</span><br><span class="line">       Thread.currentThread().stop(t);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>在绝大多数情况下，我们可以通过修改变量表示目标线程应该停止运行，来代替stop方法。当然线程要周期性的检查该变量，如果该变量表示线程要终止，那么线程需要一种有序的方式从<strong>run</strong>方法退出。为了确保停止请求能够及时响应，该变量必须用<strong>volatile</strong>修饰或者同步访问该变量。</p><p>下面假设你的applet中包含这样的一段代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Thread blinker;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        blinker = <span class="keyword">new</span> Thread(<span class="keyword">this</span>);</span><br><span class="line">        blinker.start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        blinker.stop();  <span class="comment">// UNSAFE!</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(interval);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">            &#125;</span><br><span class="line">            repaint();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>采用变量的方式，让线程的停止变的安全的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> Thread blinker;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        blinker = <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Thread thisThread = Thread.currentThread();</span><br><span class="line">        <span class="keyword">while</span> (blinker == thisThread) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(interval);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">            &#125;</span><br><span class="line">            repaint();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Thread-interrupt"><a href="#Thread-interrupt" class="headerlink" title="Thread.interrupt"></a>Thread.interrupt</h3><p>如果我们要停止一个等待周期非常长的线程，如正在等待输入的线程，就要使用<strong>Thread.interrupt</strong>方法。我们只需要在我们上面的例子的例子中改变状态的地方，加上<strong>Thread.interrupt</strong>来打断等待。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Thread moribund = waiter;</span><br><span class="line">        waiter = <span class="keyword">null</span>;</span><br><span class="line">        moribund.interrupt();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>这种方式中，任何捕获了<strong>interrupt exception</strong>的方法，如果不准备立即处理该异常，那么重新申明（reasserts）该异常是非常重要的。此处用reasserts而不用rethrows，因为这里不总是重新抛出异常。因为存在一个方法捕获了<strong>InterruptedException</strong>而不抛出这个已检查的异常，而是重新终端自己，如下面的代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Thread.currentThread().interrupt();</span><br></pre></td></tr></table></figure><p>这样就可以尽可能的保证线程能够再增加一个<strong>InterruptedException</strong>。</p><hr><p>在实际中，不是所有的线程都会响应<strong>Thread.interrupt</strong>，这个时候我们可以使用特定于应用程序之上的技巧。例如，一个线程正在一个已知的套接字上等待，我们可以关闭该套接字，使程序立即返回。但不幸的是，没有一个通用的技巧。故意拒绝服务攻击，<strong>Thread.stop</strong>和<strong>Thread.interrupt</strong>不能正常工作的IO操作等导致处于等待中的线程，都会使<strong>Thread.interrupt</strong>和<strong>Thread.stop</strong>不会被响应。</p><p>测试代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">T1</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            BufferedReader br = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(System.in));</span><br><span class="line">            System.out.println(<span class="string">"Enter your value:"</span>);</span><br><span class="line">            String str = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                str = br.readLine();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="string">"your value is :"</span> + str);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        Thread t1 = <span class="keyword">new</span> Thread(<span class="keyword">new</span> T1());</span><br><span class="line">        t1.start();</span><br><span class="line">        <span class="comment">//等待线程启动成功</span></span><br><span class="line">        Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        t1.stop();</span><br><span class="line"><span class="comment">//        or</span></span><br><span class="line"><span class="comment">//        t1.interrupt();</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="Thread-suspend"><a href="#Thread-suspend" class="headerlink" title="Thread.suspend"></a>Thread.suspend</h3><p><strong>Thread.suspend</strong>天生具有死锁倾向。如果目标线程挂起时在保护关键系统资源的管程上持有锁，则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用</p><h3 id="Thread-resume"><a href="#Thread-resume" class="headerlink" title="Thread.resume"></a>Thread.resume</h3><p><strong>Thread.resume</strong>之前需要锁定管程，则会发生死锁。这类死锁通常表现为自我“冻结”的进程。</p><p>同<strong>Thread.stop</strong>一样，谨慎的做法是“目标线程”有一个变量来表示其状态(active或者suspended)。理想的状态是暂停时，使用<strong>Object.wait</strong>让线程等待。当线程恢复时，目标线程使用<strong>Object.notify</strong>来发出通知通知。</p><p>加入你的applet程序中包含这样一段处理鼠标点击的处理逻辑，我们用<strong>blinker</strong>来表示线程状态的切换者。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">boolean</span> threadSuspended;</span><br><span class="line"></span><br><span class="line"> <span class="function">Public <span class="keyword">void</span> <span class="title">mousePressed</span><span class="params">(MouseEvent e)</span> </span>&#123;</span><br><span class="line">     e.consume();</span><br><span class="line"></span><br><span class="line">     <span class="keyword">if</span> (threadSuspended)</span><br><span class="line">         blinker.resume();</span><br><span class="line">     <span class="keyword">else</span></span><br><span class="line">         blinker.suspend();  <span class="comment">// DEADLOCK-PRONE!</span></span><br><span class="line"></span><br><span class="line">     threadSuspended = !threadSuspended;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>上面这段代码是不安全的，你可以在上面的事件处理中避免使用<strong>Thread.suspend</strong>和<strong>Thread.resume</strong>，如下所示</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">mousePressed</span><span class="params">(MouseEvent e)</span> </span>&#123;</span><br><span class="line">       e.consume();</span><br><span class="line"></span><br><span class="line">       threadSuspended = !threadSuspended;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> (!threadSuspended)</span><br><span class="line">           notify();</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>但是你需要将下面的代码加入到线程的<strong>run</strong>方法中</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(<span class="keyword">this</span>) &#123;</span><br><span class="line">                    <span class="keyword">while</span> (threadSuspended)</span><br><span class="line">                        wait();</span><br><span class="line">                &#125;</span><br></pre></td></tr></table></figure><p>由于<strong>wait</strong>方法会抛出<strong>InterruptedException</strong>，所以我们需要用try … catch 来处理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(interval);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">synchronized</span>(<span class="keyword">this</span>) &#123;</span><br><span class="line">                    <span class="keyword">while</span> (threadSuspended)</span><br><span class="line">                        wait();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">            &#125;</span><br><span class="line">            repaint();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>需要注意的是<strong>notify</strong>在<strong>mousePressed</strong>方法中，而<strong>wait</strong>在<strong>run</strong>方法的同步块中。</p><p>由于Java的同步操作代价较大，我们可以尽量减小同步块的体积，然锁占用的减少的最少。我们可以将代码再次优化为如下所示：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">boolean</span> threadSuspended;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(interval);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (threadSuspended) &#123;</span><br><span class="line">                <span class="keyword">synchronized</span>(<span class="keyword">this</span>) &#123;</span><br><span class="line">                    <span class="keyword">while</span> (threadSuspended)</span><br><span class="line">                        wait();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">        &#125;</span><br><span class="line">        repaint();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>volatile</strong>的使用此处就不展开了，具体的可以查阅相关文档。</p><hr><h3 id="Thread-destroy"><a href="#Thread-destroy" class="headerlink" title="Thread.destroy"></a>Thread.destroy</h3><p><strong>Thread.destroy</strong>从未被实现并且已经弃用。假如它被实现了，也会像<strong>Thread.suspend</strong>一样容易导致死锁。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;Thread中大量的方法已经被抛弃，很多人可能都知道它们线程不安全，本篇文章就是要详细的介绍这些不被推荐的方法背后隐藏的诸多陷阱。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;Thread-stop&quot;&gt;&lt;a href=&quot;#Thread-stop&quot; class=&quot;headerlink&quot; title=&quot;Thread.stop&quot;&gt;&lt;/a&gt;Thread.stop&lt;/h3&gt;&lt;p&gt;当一个正在运行的线程，被执行stop时，该线程会抛出一个&lt;strong&gt;ThreadDeath&lt;/strong&gt;实例。如果此时该线程已经进入&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96\&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;管程&lt;/a&gt;)中，就可能使受管程保护的对象出现不确定的状态，从而导致使用该对象的别的线程出现问题。这样的对象，我们称之为&lt;strong&gt;损坏的对象&lt;/strong&gt;。当线程操作包含损坏对象，任意的行为都会影响结果，而这些行为又是琢磨不定的。&lt;strong&gt;ThreadDeath&lt;/strong&gt;不像别的未检查异常，它会悄悄的杀死线程，不会有任何警告，从而导致程序腐化。代码腐化可能会在任何时候导致实际损害发生，甚至在未来几小时或者几天。&lt;/p&gt;
    
    </summary>
    
      <category term="技术" scheme="https://www.yangguo.info/categories/technique/"/>
    
    
      <category term="Java" scheme="https://www.yangguo.info/tags/Java/"/>
    
      <category term="线程" scheme="https://www.yangguo.info/tags/%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
</feed>
