<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title><![CDATA[foolbear的冥想盆]]></title>
  
  <link href="/atom.xml" rel="self"/>
  <link href="http://jxy.me/"/>
  <updated>2024-04-18T14:45:59.633Z</updated>
  <id>http://jxy.me/</id>
  
  <author>
    <name><![CDATA[foolbear]]></name>
    
  </author>
  
  <generator uri="http://zespia.tw/hexo/">Hexo</generator>
  
  <entry>
    <title><![CDATA[MotherDuck - DuckDB in the cloud and in the client]]></title>
    <link href="http://jxy.me/2024/04/05/motherduck/"/>
    <id>http://jxy.me/2024/04/05/motherduck/</id>
    <published>2024-04-05T03:59:42.000Z</published>
    <updated>2024-04-05T04:23:06.000Z</updated>
    <content type="html"><![CDATA[<p>看到一个很奇特的系统，虽然这个名字有些搞笑。。。<br>﻿<br>原文：<a href="https://www.cidrdb.org/cidr2024/papers/p46-atwal.pdf" target="_blank" rel="external">https://www.cidrdb.org/cidr2024/papers/p46-atwal.pdf</a>﻿<br>官网：<a href="https://motherduck.com/" target="_blank" rel="external">https://motherduck.com/</a>﻿</p>
<a id="more"></a>
<p>DuckDB是一个嵌入式的OLAP数据库，官方的slogan是：a fast in-process analytical database。某种程度上类似SQLite，但适用的场景不太一样，似乎是为了增强原有的单机数据分析场景，例如R、SPSS、Pandas等，让分析师们能用SQL Like的方式来操作数据。<br>﻿<br>这样的一种产品定位，有必要做成云服务（SaaS）么？“扩展性”这个词，似乎天生就与“嵌入式”相悖。唯一可能的场景是：有大数据集在云上（S3），拉到本地处理太过麻烦（带宽才是最宝贵的资源，而且上传/下载的带宽往往是不对称的）；处理后的结果又要和本地数据进行进一步计算，并且希望在整个过程中能保持用户体验的一致性；同时数据可以在不同用户之间方便的共享。MotherDuck就是瞄准了这样一种场景。<br>看了下创始团队，跟DuckDB似乎是两拨人，虽然有合作。创始人似乎是以前google搞BigQuery的。<br>﻿<br>简言之，MotherDuck就是Serverless DuckDB，但跟通常的分布式数据库不太一样，一些设计选择值得思考：
﻿</p>
<ul>
<li>avoids scale-out：这是有些反直觉的，云数据库可以不需要考虑scalability；MotherDuck给出的理由是，通过分析发现，超过95% 的数据库大小小于1TB，超过95%的查询涉及数据小于10GB；相比scalability带来的复杂性和额外开销（scheduling、shuffle等），scale-up/down是更合理的选择；</li>
<li>Hybrid Query Processing：查询可以横跨local client与云上，这就是DuckDB的特色了，不是嵌入式数据库的话还真做不到这点；个人感觉这也是从适应数据分析师的使用习惯上考虑的，降低云服务的门槛；换句话说，云只是local client的补充；通过引入一个特殊的bridge算子实现；</li>
</ul>
<h2 id="架构">架构</h2>
<p>无论如何，MotherDuck必然有一个client端的DuckDB实例；即使是使用WebUI，DuckDB实例也会以WebAssembly的方式运行在浏览器中：</p>
<p><img src="/2024/04/05/motherduck/1.png" alt=""></p>
<ul>
<li>控制面：常规的元数据管理、鉴权、监控等；</li>
<li>计算层：动态分配的duckling容器，每个容器中运行一个DuckDB实例；容器可能随时发生scale-up/down，也会在没有负载时自动关闭，具体的策略没有讲；</li>
<li>存储层：这个就比较常规了，对象存储 + 本地SSD Cache；可以是parquet、CSV、JSON等格式；<ul>
<li>当然除了cloud storage，也可以从本地加载数据；</li>
<li>支持快照、TimeTravel等特性；应该是扩展了DuckDB原有的存储格式，但没有讲细节；
﻿</li>
</ul>
</li>
</ul>
<p>从逻辑视图上来说，MotherDuck的数据是以database的形式来组织的，本地的database只能被local client关联，云上的database可以被多个duckling容器以只读模式关联，但只能被一个duckling读写；<br>﻿<br>此外，由于必然有一个client端的实例，result cache就变得比较简单。文中提到基于Web UI的各种自助分析（pivot、filter、agg等），其实都是基于一个client端的临时表：</p>
<figure class="highlight SQL"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="operator"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> localMemDb.main.cacheTable1 <span class="keyword">AS</span></span></div><div class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> ({userQuery})</div><div class="line"><span class="keyword">LIMIT</span> {CLIENT_CACHE_ROW_LIMIT}</div></pre></td></tr></table></figure>

<p>相比服务端的result cache实现，复杂度降低很多。</p>
<h2 id="Hybrid_Query_Processing">Hybrid Query Processing</h2>
<p>早在MapReduce时代，就有“移动计算比移动数据更经济”的说法。所谓的Hybrid Query Processing，关键也正是这一点。早些年还流行过“端云一体”，比如推荐引擎的推理模型可能是运行在手机端而不是服务端，大道趋同。<br>﻿<br>﻿﻿<img src="/2024/04/05/motherduck/2.png" alt=""><br>﻿<br>文中给出了一个执行计划的例子，数据从本地上传到云上，云上完成join操作，结果再下载回本地。从计划上来看，所谓的bridge似乎和传统的exchage有些类似？个人猜测：</p>
<ul>
<li>所谓的bridge仅用于local和remote之间的数据传递；与MPP不同，并不存在duckling数据再bridge到另一个duckling的情况；毕竟scale-up解一切，也不需要MPP了；</li>
<li>bridge也不会涉及数据的重分布，就是原样的传递；但文中特意提了下，bridge可以保证sink/source端的顺序一致，a feature expected by dataframe library users；<ul>
<li>这就有个问题了，某些情况下，是否直接将本地的文件上传到云端后计算更简单？还是取决于local的计算能力，现在一些笔记本的计算能力真未必比ECS差；</li>
</ul>
</li>
<li>bridge可以简单的看做一个“生产者-消费者”模型，为此还引入了相应的流控机制；</li>
<li>用户可以手动指定每个table scan的运行模式（MD_RUN = REMOTE / LOCAL）；</li>
<li>优化器的优化目标是：最小数据传输成本；这跟大多数优化器都不太一样；</li>
</ul>
<h2 id="DEMO">DEMO</h2>
<p><img src="/2024/04/05/motherduck/3.png" alt=""></p>
<p>这个测试case也是很有意思：巨大的事实表在云上、较小的维表在本地。相比将事实表拉到本地来处理，必然是将维表上传到云上更优，无论是从执行时间还是成本的角度来考虑。但这其实发挥不出local client + cloud的优势，因为并没能利用上local的算力；如果local很弱，还不如全在云上计算。<br>﻿<br>比较合理的case是local和cloud都要处理大量复杂计算，但又需要对结果进行一些关联。例如local侧进行一些自有业务敏感数据的分析，cloud上进行一些公开数据集的分析之类。正如官网自述：</p>
<ul>
<li>DuckDB：turns your laptop into a personal analytics engine；</li>
<li>MotherDuck：scales your laptop into the cloud；</li>
</ul>
<h2 id="总结">总结</h2>
<p>总体看下来，关键就两点：1. empower users to use more client-side resources；2. simplifying cloud data system architecture by using a scale-up approach；具体效果不好说，产品似乎也还未上线，官网也是那种涂鸦风，真的不是toy project么。。
﻿</p>
<h2 id="附录：Scalability!_But_at_what_COST?">附录：Scalability! But at what COST?</h2>
<p>文中提到的这篇文章，也比较老了。大意是很多系统看着扩展性很好，但实际上并没有经过充分优化（甚至不如一个单线程实现），扩展性好 ≠ 性能好。<br>﻿﻿<br><img src="/2024/04/05/motherduck/4.png" alt=""><br>﻿<br>当初MapReduce有类似的争议：<a href="https://www.cs.utexas.edu/~rossbach/cs378/papers/dewitt08blog-mapreduce-backwards.pdf" target="_blank" rel="external">MapReduce: A major step backwards</a>。现在看来，更多是一种权衡吧，编程范式的简化必然导致无法达到极致性能。这篇文章也并不是否定所有分布式系统，而是希望在作出各种设计选择前，更多方面考虑一下。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>看到一个很奇特的系统，虽然这个名字有些搞笑。。。<br>﻿<br>原文：<a href="https://www.cidrdb.org/cidr2024/papers/p46-atwal.pdf" target="_blank" rel="external">https://www.cidrdb.org/cidr2024/papers/p46-atwal.pdf</a>﻿<br>官网：<a href="https://motherduck.com/" target="_blank" rel="external">https://motherduck.com/</a>﻿</p>
]]>
    
    </summary>
    
      <category term="bigdata" scheme="http://jxy.me/tags/bigdata/"/>
    
      <category term="paper" scheme="http://jxy.me/tags/paper/"/>
    
      <category term="分布式" scheme="http://jxy.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[随便写写]]></title>
    <link href="http://jxy.me/2023/10/03/just-say-something/"/>
    <id>http://jxy.me/2023/10/03/just-say-something/</id>
    <published>2023-10-03T02:07:29.000Z</published>
    <updated>2023-10-03T03:58:35.000Z</updated>
    <content type="html"><![CDATA[<p>许久未码字了，想诈个尸。建了个新文档，对着屏幕却犹豫不知该写点啥。</p>
<a id="more"></a>
<p>距离上篇已经整整2年。。。原因也很简单，累、懒。<br>近期工作当得起“身心俱疲”四个字，下班后只想躺尸。运气不好的话，下班也和上班没什么区别。中秋那天去了丈母娘家，结果一直有人电话会议找我，不是这里宕机就是那里查询慢，饭都来不及吃，从中午折腾到半夜。<br>这种全靠人肉保障的稳定性是无法持久的，而且也是可以改善的，只要老板们下定决心投入。随着各种不可抗力，人越来越少，未来堪忧啊。。。</p>
<p>这让我不得不吐槽，我们在考虑各种“全球领先架构”之外（团队去年还是发了一篇VLDB的，虽然跟我关系不大），是否该把目光更多放到一些基础的事情上：稳定性、产品体验、文档、诊断工具、管控体系。。。</p>
<p>传统的OLAP，或者说Query Engine，好像还是那些东西，向量化、在离线一体、存算分离、adaptive等等。现在大家更多涌入了新的方向，serverless和native。所以最近1年多以来，我经常和k8s打交道，满脑袋都是operator、Pod、PVC、sidecar等等，虽然没有直接参与底层k8s scheduler的开发，也算是弥补了我<a href="/2021/10/07/sth-about-scheduling/">之前</a>说不太了解k8s的遗憾。实现了一些技术突破，如节点的无状态改造和热启动，从30s+缩短到1s，还挺有意思的。再结合JVM Snapshot等技术，能玩的花样就更多了。<br>基于传统的ECS甚至物理机，能实现serverless么？我觉得也可以吧，serverless只是种产品形态，没谁说k8s就等于serverless。但裸金属+容器编排已是大势，也不必要自己创造困难走弯路对不。<br>说到产品形态，其实我一直很喜欢snowflake这种，简单，不暴露太多东西给用户。我们的话，暴露给用户的参数得有20+个吧。。。</p>
<p>至于native，因为有velox这个可参照物，路线没那么曲折，虽然中间也有些争论，到底是直接搞一个native worker还是通过JNI方式逐步改造。下半年会更多参与吧。不过我有预感，又有一大波稳定性问题在路上了。</p>
<p>另外最近还在搞Remote UDF这个东西，对接类似aws lambda的外部服务。这part事情更多是防守性的吧，从效率上来讲，调用云上的另一个服务肯定不是UDF的最优解。</p>
<p>林林总总，还有各种杂事。事情做了不少，但很难像以前那样长篇大论。还是累+懒吧。最近总是被B站的鬼畜洗脑，“有时候找找自己的原因好吧，这么多年工资涨没涨，有没有认真工作”。说的好啊，充满着来自成功人士的俯视。</p>
<p>说到B站，我也是十几年老用户了，持股从150刀跌倒现在的10几刀都没卖，也算是真爱粉了吧。。。希望下次码字的时候，B站能回本吧。。。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>许久未码字了，想诈个尸。建了个新文档，对着屏幕却犹豫不知该写点啥。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Something about Scheduling & YARN]]></title>
    <link href="http://jxy.me/2021/10/07/sth-about-scheduling/"/>
    <id>http://jxy.me/2021/10/07/sth-about-scheduling/</id>
    <published>2021-10-07T07:19:34.000Z</published>
    <updated>2021-10-07T08:28:30.000Z</updated>
    <content type="html"><![CDATA[<p>也许应该叫“调度那些事”。</p>
<a id="more"></a>
<p>前段时间我司的调度系统发了一篇论文<a href="https://www.usenix.org/system/files/atc21-feng-yihui.pdf" target="_blank" rel="external">《Scaling Large Production Clusters with Partitioned Synchronization》</a>，荣获ATC 2021 best paper，很是让人艳羡。我对YARN比较熟悉，论文跟我现在的工作也算有些关联，所以特意去读了下。结合自己的一些思考，总结下。</p>
<h2 id="什么是调度">什么是调度</h2>
<p>在技术领域，提到“调度”这个词，大家第一反应是啥？crontab？内核、时间片、CFS？<br>这个词还是太宽泛了，红绿灯不也是交通调度系统么？我们加一些限定条件：大数据或分布式系统领域。</p>
<p>借用我司fuxi调度系统的分类，大概有以下几种：</p>
<ul>
<li>资源调度：典型的就是YARN和Mesos等分层调度系统，虽然现在提的不多了。这类系统的特点是：无业务逻辑，着眼于资源管理、分配与隔离。最大的意义在于将资源调度逻辑从应用中解耦出去；<ul>
<li>这种分层调度系统还可以出现一些很好玩的“嵌套”，比如k8s on YARN、YARN on fuxi、fuxi on k8s等等；</li>
<li>理论上，A on B on C也是可行的，不过禁止套娃；</li>
</ul>
</li>
<li>任务调度：传统意义上的调度系统，也包括各种DAG调度系统。关注任务之间的依赖关系、failover、重跑等；<ul>
<li>比如早期的jobtracker、spark中的TaskScheduler、presto中的NodeScheduler等；</li>
<li>也包括各种DAG调度，azkaban/airflow等；</li>
</ul>
</li>
<li>数据调度：这个比较特殊，大意是关注数据的locality，如何在不同的机房/region之间调度数据尽量避免跨region读；<ul>
<li>可能是特定场景才有用，开源领域没有看到类似的系统；</li>
<li>有那么点像缓存一致性问题；</li>
</ul>
</li>
<li>单机调度：只有分层调度+混合负载时才会有这类问题，如何做到单机超卖与多种workload的平衡；</li>
</ul>
<p>这种分类的方法未必是科学的，比如spark的DAGScheduler如何分类？DAGScheduler严格来说是不是应该叫做DAGPlanner、DAGSplitter，而不是Scheduler？<br>这就已经是玄学的领域了，还是不碰为妙。正如那个TwoHardThings joke: “There are only two hard things in Computer Science: cache invalidation and naming things.”。</p>
<p>常见的还是资源调度和任务调度。</p>
<p>我个人理解，所谓的调度，是一个“撮合”的过程。撮合的可以是资源请求与物理资源、可以是task与node、可以是split与task等等等等。这个“撮合”的过程，以YARN为例，被称作“assignment/allocation”，在YARN中这两个术语是混用的。</p>
<p><img src="/2021/10/07/sth-about-scheduling/1.png" alt=""></p>
<p>上图可以认为是一个极简的调度系统模型（参考YARN）：</p>
<ul>
<li>client提交request给master，每个request有自己的调度约束：需要的资源大小、SLA等等，master汇总所有request的状态；</li>
<li>worker节点心跳向master汇报状态，master汇总所有node的状态；</li>
<li>master根据已知的全局状态（request + node），进行撮合（assignment/allocation）；</li>
</ul>
<p>另一种理解调度系统的角度，就是把它看做一个决策系统，而决策系统的核心是什么？</p>
<ul>
<li>输入：就是上文提到的master所需的全局状态（request + node）；</li>
<li>决策逻辑：如何撮合，有哪些约束条件？公平？先到先得？是否有抢占？</li>
<li>输出：最终做出的决策，哪些request分配到哪些node、哪些split分配给哪些task等；</li>
</ul>
<p>不管是单一的调度系统、分层调度系统，还是现在糅合了“编排”概念、大有一统天下之势的k8s，其调度过程本质都类似。</p>
<h3 id="题外话：调度_vs_编排">题外话：调度 vs 编排</h3>
<p>既然提到了k8s，就不得不聊聊所谓的“编排”，这个翻译还是比较拗口的。我对k8s并不熟，只是写下自己的理解。</p>
<p>调度（schedule）和编排（orchestration），到底是啥区别？早期的各种文章，会将DAG调度（oozie/airflow）之类也称作orchestration systems。但这种不在讨论范围之内。</p>
<p>个人理解，调度（schedule）指的是单次决策（上文中的“撮合”）的过程。assignment后，本次调度便结束了。而编排（orchestration），更多是强调“自动化”，通过各种手段，包括但不限于调度，使app保持在特定状态。“调度”是相对原子化的，而“编排”是一个人更上层的、持续的过程。</p>
<p>以k8s中的Deployment为例，用户需要描述app的最终状态：我要某个服务部署在3个节点上，暴露一个域名，还要LB。k8s master会通过多次的调度行为、各种配置的修改，来满足用户的需求。并持续监控app的状态，如果状态不一致会采用各种手段自动恢复。在这个“编排”的过程中，“调度”行为会发生多次，也是其中最重要的步骤，但不是唯一。</p>
<h3 id="题外话：推_vs_拉">题外话：推 vs 拉</h3>
<p>另一个有趣的话题，为何大多数调度系统，甚至说大多数主-从的分布式系统，都是基于心跳的pull模式，而不是master主动去轮询所有节点的状态并推送任务？<br>推模式也不能说少见，没记错的话，spark和presto都是推模式，或者说是推/拉结合（task结束触发新的推送）。但单纯的推模式有些弊端：</p>
<ol>
<li>master增加了额外的工作和错误处理逻辑，更容易成为全局的瓶颈；<ul>
<li>比如有慢节点导致master轮询时卡住了，master是等还是不等？</li>
</ul>
</li>
<li>推模式下，集群规模更受限，毕竟每次都要轮询所有节点；<ul>
<li>如果每次轮询一部分节点呢？感觉也不是不行，但何必给自己增加难度？</li>
</ul>
</li>
</ol>
<p>相对的，异步的pull模式能支撑更高的吞吐和规模。</p>
<h2 id="调度系统的复杂性">调度系统的复杂性</h2>
<p>综上，“调度”这个行为是很单一的：根据现有的全局状态做出决策。但为啥现有的各种调度系统，无论开源/闭源，都这么复杂？YARN有超过110w行代码，HDFS也不过150w左右。即使刨除各种外围代码，只看核心的调度器部分（scheduler），也是很复杂。<br>很大程度上是因为调度约束（constraint）实在太复杂了（也可以叫做调度策略），或者说在撮合的过程中，要考虑的因素太多了。</p>
<p>常见的soft constraint（尽量满足）：</p>
<ul>
<li>Locality：数据本地性，一般分为NODE_LOCAL/RACK_LOCAL/OFF_SWITCH等几个级别；<ul>
<li>这也是最常见的soft constraint。调度器会尽量满足每个requset的locality，但不保证；</li>
<li>话说，现在Locality似乎没有以前重要了，很大程度上是因为带宽资源没那么紧张了；以前有个说法：移动计算比移动数据更经济。因为都是千兆网卡，最多几个千兆做个bond，现在都是万兆起步了；</li>
</ul>
</li>
<li>Fairness/Priority：多租户环境下，如何分配资源；调度器可以根据特定的公平算法，尽量保证公平；</li>
</ul>
<p>常见的hard constraint（必须满足）：</p>
<ul>
<li>Quota/ResourceLimits：如果超过了quota，一个request是不可能被调度的；<ul>
<li>quota是有很多层次的，除了常见的队列级别（queue level quota），还可能有user、app、app type等；</li>
</ul>
</li>
<li>Node Label/Partition：根据节点的属性进行调度；包括inclusive/exclusive partition等；<ul>
<li>YARN里面做的更复杂一点，细分为Node Label和Node Attribute，而且可以用DSL来写约束条件，比如：这个task要放在python版本大于xx并且tensorflow版本小于xx的节点；</li>
</ul>
</li>
<li>Affinity/Anti-Affinity：亲和性与反亲和性，某些task必须调度到同一个节点、或者不能调度到同一个节点；<ul>
<li>例：hbase的task和zk的task调度到一起；高优先级的、在线服务的task，不能和低优先级、batch的task调度到一起；</li>
</ul>
</li>
<li>Gang Scheduling：all or nothing的调度模式；</li>
<li>其他：DAG也算是一种约束（上游任务不结束，下游不能调度）；时间（不到特定时间，任务不能调度）；</li>
</ul>
<p>为了满足各种调度约束，调度器还会引入一些更复杂的处理机制：抢占（Preemption）、预留（Reservation）等等。</p>
<p>凡此种种，导致调度器本身的逻辑不断膨胀，代码也同步膨胀。当然不同的调度系统可以选择支持或不支持某些约束。<br>如果不考虑调度约束，那就简单了，只要node还活着，XJB乱调就行了，不如称之为“Monkey Scheduler”，正如排序算法中的猴子排序。</p>
<p>除了核心的调度器逻辑之外，作为一个完整的调度系统，肯定还要考虑其他一些事情。</p>
<ul>
<li>从产品和用户的角度：<ul>
<li>如何支持多租户？YARN是用队列实现的，但并不是唯一解；</li>
<li>单一负载 or 混合负载？</li>
<li>资源如何抽象？slot？mem和vcore？flops？</li>
<li>如何计量/计费？</li>
</ul>
</li>
<li>从工程实现的角度：<ul>
<li>调度器决策需要维护大量的全局状态（request+node），这些状态如何同步与持久化？</li>
<li>状态又会带来failover与HA的一堆问题；</li>
<li>资源如何隔离？如何超卖？</li>
<li>资源碎片如何处理？如何提高资源利用率？</li>
</ul>
</li>
</ul>
<h2 id="调度系统的衡量指标">调度系统的衡量指标</h2>
<p>借用论文中的总结，调度系统需要关心的指标包括：</p>
<ul>
<li>scheduling efficiency：这是一个比较学术的词。从工程的角度来说，就是吞吐：你的系统每秒能支持多少调度请求？换句话说，你的调度器每秒能做出多少assignment决策？</li>
<li>scheduling quality：不同系统对quality定义可能不同。典型的就是各种soft constraint（比如locality）是否能被满足；<ul>
<li>quality和efficiency往往是互斥的，如果要精准match每个request，吞吐就上不去；</li>
</ul>
</li>
<li>fairness and priority：我感觉这个也可以算作quality；</li>
<li>resource utilization：好的调度系统是能尽量将集群资源用满的，可能是通过超卖、碎片整理等手段；</li>
</ul>
<p>此外，从工程的角度来说，还要补充个很重要的指标，就是集群规模。实践中的各种系统大多是基于心跳向master汇报状态的，集群规模一大，光是心跳就能将master打挂。</p>
<p>不同的调度系统，对各个指标的偏向性是不同的。一般来说，短时task、batch的调度系统，更关注吞吐（即使某些soft constraint不能满足，也影响不大，反正任务很快结束）；而长时任务、在线服务的调度系统，更关心调度质量。</p>
<h2 id="工程实践">工程实践</h2>
<p>上面讨论的都偏学术、偏抽象，接下来讨论点实际的。以YARN为例，看看它在各方面存在的问题和可能的解法。</p>
<ul>
<li>efficiency：YARN的调度器单机吞吐其实一直都不高，通过SLS等模拟可能就1k左右，这可以说是YARN的设计缺陷导致；</li>
<li>quality：支持的调度约束很丰富。但主要用于batch场景，关注点也不在quality；</li>
<li>fairness：支持公平算法+抢占；支持各个粒度的quota限制；支持队列级别的优先级；</li>
<li>resource utilization：一般而言也不高，因为YARN的中心化调度机制导致NM必然有一段时间是空跑的；</li>
<li>规模：实践中有2k~3k的，再大规模估计也很难，主要还是心跳问题。</li>
</ul>
<h3 id="演进1：提升单机吞吐；">演进1：提升单机吞吐；</h3>
<p>YARN调度器的特点：</p>
<ol>
<li>NM周期性向RM汇报心跳，触发NODE_UPDATE事件，调度器会尝试在这个节点上分配container；</li>
<li>默认配置下，每次心跳只分配一个；</li>
</ol>
<p>以hadoop 3.3.1为例，代码截图：<br><img src="/2021/10/07/sth-about-scheduling/2.png" alt=""></p>
<p>时序图：<br><img src="/2021/10/07/sth-about-scheduling/3.png" alt=""></p>
<p>这种设计的好处：NM发心跳过来了，至少说明它肯定是活着的，调度器能“看到”这个节点最新的状态，做出的assignment是“明确”的。<br>但弊端也很明显：吞吐受限于NM的心跳，很难提升；如果缩短NM心跳的间隔，又可能会带来其他的问题；</p>
<p>暂且称这种方式为同步调度。一个很自然的想法，能否在NM没有心跳发过来的时候，调度器自己异步的进行assignment？本质上，心跳是用于更新node state的，和assignment是两个过程，可以解耦。<br>FairScheduler和CapacityScheduler都有这方面的尝试。FairScheduler中叫做ContinuousScheduling，不过已经废弃；以CapacityScheduler中的GlobalScheduling（<a href="https://issues.apache.org/jira/browse/YARN-5139" target="_blank" rel="external">YARN-5139</a>）为例：</p>
<p><img src="/2021/10/07/sth-about-scheduling/4.png" alt=""></p>
<p>本质上，类似两阶段提交：</p>
<ol>
<li>RM中有多个线程，周期性的、异步的进行assignment；</li>
<li>由于不等待NM的心跳，所以这些线程看到的状态可能是“不新鲜”的，做出的决策可能也是错误的；<ul>
<li>一个例子：线程1看到节点A上有空闲资源，分配掉了；但线程2不知道，仍会尝试在节点A上分配任务；</li>
<li>简单的解法就是加锁，但吞吐又会受影响；</li>
<li>这是和同步调度最大的区别。同步时，assignment必然是“明确”的；</li>
</ul>
</li>
<li>所以每个线程的决策只是一个提议（proposal），是否能真正提交变为一个assignment，需要有一个全局的committer进行仲裁；<ul>
<li>这个committer是一个全局单例；</li>
</ul>
</li>
<li>各个线程读取全局状态时需要加readLock，只有NM心跳或committer更新全局状态时，才需要writeLock；</li>
</ol>
<p>和所有两阶段系统类似，这种设计也面临几个问题：</p>
<ol>
<li>仲裁逻辑是什么？目前的实现就是简单的比较proposal提交时间；</li>
<li>如何缓解冲突？目前有一些简单的方法，比如错开各个线程的调度时间、错开尝试的节点等；</li>
</ol>
<p>结合YARN后续的一些优化（包括assignMultiple、锁粒度优化、序列化相关优化等），社区给出的测试结果是单机吞吐可以达到5k左右。</p>
<h3 id="演进2：多调度器；">演进2：多调度器；</h3>
<p>单机吞吐的提升终究有极限，而且也不能解决规模的问题。另一种思路，就是既然一个调度器搞不定，用多个调度器行不行？大体的思路就是放松全局状态的一致性（弱一致并且不在关键的调度路径上），换取吞吐和规模的提升。<br>一个关键问题：状态（request+node）如何拆分？</p>
<ul>
<li>request：目前已有的各种实现，request都只会发送到特定的某个scheduler；换句话说，不同的scheduler，只能看到自己的request；</li>
<li>node：这里就有两个流派了；<ul>
<li>shared state：每个scheduler看到的node state是相同的，都是全局视角；</li>
<li>static partitioning：每个scheduler只能看到一部分node state；但这会带来一个问题，scheduler的调度规模受限，需要用其他手段解决；</li>
</ul>
</li>
</ul>
<h4 id="shared_state">shared state</h4>
<p>shared state的概念最早是<a href="https://research.google/pubs/pub41684/" target="_blank" rel="external">Google Omega</a>提出的：</p>
<p><img src="/2021/10/07/sth-about-scheduling/5.png" alt=""></p>
<p>简言之：</p>
<ol>
<li>多个调度器周期性的从某个角色同步node state到本地；</li>
<li>每个调度器根据自己的local state进行决策；</li>
</ol>
<p>跟YARN中GlobalScheduling的问题类似：</p>
<ol>
<li>冲突如何解决？全局加锁肯定是不行的，google给出的解法是乐观机制，假设冲突很少；如果出现冲突，理论上应该每个node自己仲裁，scheduler重试；</li>
<li>如何缓解冲突？论文中没提，各显神通了；</li>
</ol>
<p>一个很标准的shared state实现就是<a href="https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-boutin_0.pdf" target="_blank" rel="external">Microsoft Apollo(2014)</a>：</p>
<p><img src="/2021/10/07/sth-about-scheduling/6.png" alt=""></p>
<p>标准的不能再标准了。。。</p>
<h4 id="ParSync">ParSync</h4>
<p>终于到了那篇ATC best paper的核心内容。它本质解决的是shared state模型中的第二个问题：如何缓解冲突。</p>
<ol>
<li>omega假设每个调度器同步node state的时间很短，代价很小；但fuxi实际的观察中，同步代价很高；而且集群规模上去后，这个状态会很大；</li>
<li>“不新鲜”的状态导致冲突几率增加（论文中有详细论证），问题进一步恶化；<ul>
<li>这也是很符合直觉的；正如YARN的同步调度为何选择有节点心跳时才触发调度逻辑；</li>
<li>我猜测这个结论是来自于实际的经验总结，然后才进一步用数学化的方法建模论证；</li>
</ul>
</li>
</ol>
<p>论文提出了一种被称作ParSync的解法：</p>
<ol>
<li>将node state分区，每个调度器每次同步状态时，只同步一个分区的状态，很快就能完成；</li>
<li>这样，每个调度器看到的状态，有部分是新鲜的，有部分是不新鲜的；</li>
<li>调度时，调度器优先在状态新鲜的节点上调度，以减少冲突几率；</li>
<li>但新鲜的节点毕竟是少数，只在这些节点上调度会导致quality下降（比如locality无法满足）；调度器提供几种模式给用户选择，是latency-first还是quality-first；</li>
</ol>
<p>其实吧，论文的核心内容就这些。其他的数学公式、实验数据等只是为了支撑这个论点。</p>
<h4 id="static_partitioning">static partitioning</h4>
<p>这个流派的代表是YARN Federation。<br>微软2019年发表了基于YARN开发的<a href="https://www.microsoft.com/en-us/research/publication/hydra-a-federated-resource-manager-for-data-center-scale-analytics/" target="_blank" rel="external">Hydra调度系统</a>，将之前的Apollo替换掉了（不知为啥），并将大部分feature回馈给社区。</p>
<p><img src="/2021/10/07/sth-about-scheduling/7.png" alt=""></p>
<ol>
<li>多个sub-cluster组装成一个大的cluster，对外API不变；</li>
<li>引入一个极其重要的角色AMRMProxy，用于不同sub-cluster之间的路由；<ul>
<li>单个scheduler是不知道其他sub-cluster的存在的，全靠AMRMProxy透明的代理掉；</li>
<li>AMRMProxy有点类似微服务中的sidecar模式；</li>
</ul>
</li>
<li>引入另一个角色GPG负责汇总全局状态，但不在调度的关键路径上；</li>
<li>引入全局的state store用于存储各种路由策略；</li>
</ol>
<p>据论文中的披露，微软维护着目前已知最大的YARN集群，单集群5w+节点，由20~30个sub-cluster组成，每个sub-cluster 2k~3k节点。<br>个人感觉，相对于shared state，这种工程实现上要更复杂一些，暂且没有看到特别突出的优点。<br>也许最大的好处是可以尽量复用YARN已有的各种能力。</p>
<h3 id="演进3：Distributed_Scheduling">演进3：Distributed Scheduling</h3>
<p>同样来自微软的贡献。YARN中可以开启<a href="http://hadoop.apache.org/docs/r3.3.1/hadoop-yarn/hadoop-yarn-site/OpportunisticContainers.html" target="_blank" rel="external">OpportunisticContainers</a>特性使用。某些情况下，调度行为可以不经过master，直接将任务提交给worker。<br>对于短时的、batch任务效果非常明显，因为省去了中间的调度开销，提升资源利用率和吞吐。</p>
<p><img src="/2021/10/07/sth-about-scheduling/8.png" alt=""></p>
<h3 id="其他思路">其他思路</h3>
<p>最近还看到个画风不太一样的系统：facebook的<a href="https://engineering.fb.com/2020/04/21/data-infrastructure/turbine/" target="_blank" rel="external">流处理调度系统Turbine</a>：</p>
<p><img src="/2021/10/07/sth-about-scheduling/9.png" alt=""></p>
<p>论文看下来，这个系统是比较特异化的，估计也只适用于他们的场景，但其中有一个思路很有意思：事中调整替代事前决策（fast scheduling），与其在事前考虑各种约束、资源大小等等，不如事中通过LB/scale-up等方式动态调整。<br>这个思路也只适用于长时任务，对于batch等短时任务，根本没有事中调整的机会。</p>
<h2 id="一些总结">一些总结</h2>
<p>本来只是想总结下fuxi那篇论文的读后感，写着写着内容就慢慢越来越多。<br>遗憾的是我对k8s还是不够了解，不能更多的横向对比下。有一个孵化器项目<a href="https://yunikorn.apache.org/" target="_blank" rel="external">yunikorn</a>，稍微看了下，感觉就是将YARN支持的各种调度约束移植到k8s。</p>
<p>现代的调度系统之所以会成为一个全局的基础设施，很大程度上是因为在云原生的大背景下，混合负载的重要性前所未有的提高了。以前，每个系统自己搞一堆机器，自己玩就行了。现在，我们要弹性、要serverless、要在离线一体化，所以一个大一统管理所有资源的调度框架，提升利用率压低成本，非常迫切。k8s之所以能快速成熟并且流行，很大因素是切中了这个时代趋势。<br>YARN其实也看到了这个趋势，在LRS（long running service）上的努力也一直未停止过，融合了早期的Apache Slider等项目，推出了<a href="http://hadoop.apache.org/docs/r3.3.1/hadoop-yarn/hadoop-yarn-site/yarn-service/Overview.html" target="_blank" rel="external">YARN Services</a>等feature。但不得不说，YARN从基因上就并不适合在线服务，在业界也并没有成功的大规模验证过，更多还是用于离线batch。而k8s是从在线向离线演进，天然与容器技术结合，难度就更小一些，也有了spark on k8s等成功的范例。虽然支持的各种约束/策略没有YARN丰富，但这些可以慢慢完善。</p>
<p>之前看过一种论调，云原生的未来必定是裸金属+容器，深以为然。以前所谓的“上云”只不过是将服务搬到云上的虚拟机。</p>
<p>写到最后，聊聊hadoop。最近总是有不少文章宣传着“<a href="https://www.infoq.cn/article/eiUpX33QqDM7LBbRVqNl" target="_blank" rel="external">hadoop已死</a>”，前几年CDH/HDP合并，前段时间又有很多hadoop相关的apache项目退休，有不少我还用过。<br>我11年开始接触hadoop，从0.20.x/1.x/2.x/3.x一路走来，10年了，也算是见证整个生态兴起、蓬勃与沉寂。个人感觉，死倒不至于，但确实式微了：</p>
<ol>
<li>hadoop生态本身就过于复杂了，组件太多不是好事，我也觉得很难用。。。随着clickhouse/ES等一站式解决方案兴起，用户都会用脚投票的。这方面的一个正面例子是snowflake：极致的产品易用性，性能反而是其次。</li>
<li>云的冲击：云化的大趋势是不可逆的，而hadoop本身并不是为云而设计，自然会被后来者超越；</li>
</ol>
<p>hadoop本身，以后应该会隐藏的更深，甚至对用户不可见。但hadoop留下的各种标准，会继续被后来者兼容，比如HDFS/HiveMeta等等。</p>
<p>但话说回来，10年本就是不短的时间了，太多的事情可以改变，hadoop也不过是沧海一粟罢了。我们已经见识了房价飙升、巨头兴起、贫富差距、P2P暴雷等等大事件，这点事算个啥。。。<br>即使是科技界，也从不缺热点，前些年的深度学习、AI、区块链，更远些的ios、移动互联、IOT、微服务，现在的k8s/云原生/serverless，还有更玄幻的“元宇宙”、脑机接口。</p>
<p>笑看谁主沉浮吧。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>也许应该叫“调度那些事”。</p>
]]>
    
    </summary>
    
      <category term="bigdata" scheme="http://jxy.me/tags/bigdata/"/>
    
      <category term="YARN" scheme="http://jxy.me/tags/YARN/"/>
    
      <category term="调度" scheme="http://jxy.me/tags/%E8%B0%83%E5%BA%A6/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Struggle to live]]></title>
    <link href="http://jxy.me/2020/11/05/struggle-to-live/"/>
    <id>http://jxy.me/2020/11/05/struggle-to-live/</id>
    <published>2020-11-05T09:21:55.000Z</published>
    <updated>2020-11-15T07:02:49.000Z</updated>
    <content type="html"><![CDATA[<p>俗务缠身，难得有个僻静的地方可以写些东西。</p>
<a id="more"></a>
<p>更新时间成功变为以“年”为单位。。。原因嘛，一是忙，二是懒，三是接收的知识量太多了。夸张点说，每天都能接触一些新鲜的东西。见到感兴趣的，我总是会先记下来，后续再慢慢研究，结果就越积越多。修福报之外也基本就是躺尸，于是blog的更新就完全随缘了。。。不过，抽空一口气写完，还是挺有成就感的。</p>
<h2 id="《深度学习推荐系统》">《深度学习推荐系统》</h2>
<p>很赞的一本书，偏算法。周末本来想随便看看，结果停不下来，一个下午+晚上一口气看完，很爽。<br>与我而言，这本书非常适合，解决了心中的很多疑惑。很大程度上是因为之前已经有了一些基础和了解，只是不成体系。<br>相比一些老掉牙的推荐系统介绍书籍（说的就是《推荐系统实践》），这本书才是真正紧跟技术潮流，也有一定深度。<br>InfoQ上还有一个<a href="https://www.infoq.cn/theme/49" target="_blank" rel="external">专栏</a>，内容是本书的一些节选。<br>总结下各种感想，不只是书中的内容。</p>
<h3 id="关于特征工程">关于特征工程</h3>
<p>前段时间看了特征工程的一些文章。有句话说的很好：数据和特征决定了机器学习的上限，算法和模型只是尽可能逼近这个上限。虽然这个上限是啥，很难定义。</p>
<p>总结各种模型的演化，有两个趋势：</p>
<ol>
<li>更复杂的特征表示 + 自动特征交叉，即所谓的“表征学习”，比如DeepWide中Wide相关的各种优化；</li>
<li>网络结构的突破，比如残差网络提升收敛速度、注意力机制、序列模式等等；</li>
</ol>
<p>现在的很多模型还是在第一个方向上发力，随便改改就能“创造”一个新的模型。。。而网络本身的优化会更难一些。</p>
<p>特征工程的本质就是数据建模：你应该以什么样的方式去定义、理解你的数据，背后是对自己数据、对业务的深刻理解。以前只能靠人工来组合和选择特征，大多就是凭经验，很苦逼。现在的趋势是向自动特征工程转变。</p>
<p>常见手段：</p>
<ul>
<li>缺失值处理：“缺失”本身也是一种特征；</li>
<li>数值特征：离散化、标准化与缩放、数据平滑；</li>
<li>类别特征：one-hot、multi-hot、序列编码、embedding；</li>
<li>多值特征、时序特征：简单做法就是embedding后再pooling；也有更复杂的处理，比如注意力机制；</li>
</ul>
<p>话说，我一直搞不太清楚sparse/dense特征的区别。从字面来看倒是很好理解，但稀疏/稠密的分界点在哪里？某个特征，有n个0就是稀疏，n-1个0就是稠密？只知道，one-hot后的特征大多是sparse的，embedding都是dense的。</p>
<h3 id="前深度学习时代">前深度学习时代</h3>
<p>经典模型的好处就是可解释性强，易于训练和部署。</p>
<p>借用书中一张图（根据书中插图自己画的）：<br><img src="/2020/11/05/struggle-to-live/4.png" alt=""></p>
<p>最经典的推荐算法应该是协同过滤（CF），直到现在，ItemCF和UserCF也是最常见的召回策略。<br>由CF又引申出矩阵分解（MF），将共现矩阵分解为用户矩阵和物品矩阵，本质是找到一些“隐向量”将user和item关联，解决CF中无法处理向量稀疏，泛化能力弱的问题。这里其实也有一些embedding的思想了：用一个低维向量来表示item和user。<br>话说我第一个知道的推荐算法其实就是CF，那还是hadoop 0.2x的时代，当时好像没啥现成的库，算法同学们在MR里用3个嵌套for循环计算矩阵乘法，印象深刻。。。</p>
<p>矩阵分解常用手段：</p>
<ul>
<li>特征值分解：只能用于方阵；</li>
<li>奇异值分解（SVD）：要求矩阵是稠密的，复杂度O(mn^2)；</li>
<li>梯度下降：找到函数的局部最小值；过拟合的问题还是存在的，需要L1/L2正则化；</li>
</ul>
<p>这个时候的推荐系统，应该叫做“洪荒时代”，还没有“模型”的概念。但其实user和item的ID也是一种特征，CF可以看做是只使用了ID一个特征的算法。很自然的，为了提升算法效果，需要引入更多特征，于是一系列经典的机器学习模型粉墨登场。</p>
<ul>
<li>LR：经典到不需要介绍。缺点在于表达能力不强，无法做特征交叉；为啥特征交叉如此重要？“辛普森悖论”了解下；</li>
<li>POLY2：特征自动两两交叉；会导致原本就稀疏的数据更加稀疏，而且训练复杂度上升到O(n^2)；</li>
<li>FM：为每个特征学习一个隐向量；特征交叉时使用隐向量内积作为权重，降低训练复杂度；</li>
<li>FFM：为每个特征学习一组隐向量，分特征域；特征交叉时使用对应特征域的隐向量内积；效果更好，但训练复杂度又上升了；</li>
<li>GBDT + LR：特征工程模型化的开始，自动生成并筛选特征组合；跟现在的embedding层很像，真正实现所谓的“端到端”训练；<ul>
<li>FM/FFM等只能做二阶的特征交叉，而GBDT无此限制；</li>
<li>GBDT进行特征转换、LR进行CTR预估，是两个独立的过程；</li>
</ul>
</li>
<li>LS-PLM：简单点说，就是对样本先做多分类（softmax），然后在每个分类内部训练LR模型，最终的CTR，是每个分类内部CTR的加权平均；<ul>
<li>脱胎于电商场景：在训练一个女装item的CTR模型时，明显不希望受到男装item数据的影响；</li>
<li>从某种意义上来说，已经有神经网络的雏形；</li>
</ul>
</li>
</ul>
<h3 id="深度学习时代">深度学习时代</h3>
<p>我记得深度学习刚开始火起来的时候，还被称作“无监督的特征学习”。深度神经网络（DNN）NB的地方就在于可以从原始的特征中学习到更复杂的特征，比如图像识别中那个经典的“点 -&gt; 边 -&gt; 物体”的例子。从这个角度来说，DNN天生就适合用来解决特征交叉的问题。</p>
<ul>
<li>深度学习模型的表达能力更强，能挖掘出更多数据中潜藏的模式；</li>
<li>深度学习的模型结构非常灵活，能根据业务场景和数据特点，灵活调整；</li>
</ul>
<p>同样借用一张图：<br><img src="/2020/11/05/struggle-to-live/5.png" alt=""></p>
<ul>
<li>AutoRec：自编码器，预测用户对物品的评分；单隐层的神经网络，结构上跟word2vec一致；</li>
<li>Deep Crossing：embedding+多层神经网络的始祖；<ul>
<li>embedding层：稀疏的类别型特征转换为稠密的embedding向量，数值型特征不需要进行embedding，直接进入stacking；emb层一般是简单的全连接</li>
<li>stacking：拼接embedding和数值特征；</li>
<li>MLP层：多层残差网络（每两层增加一个短路：shortcut + element-wise plus）</li>
<li>scoring：一般是LR</li>
</ul>
</li>
<li>NeuralCF：传统MF的加强版，广义矩阵分解模型；<ul>
<li>传统的MF中，分解后的item/user矩阵其实就是embedding，但表达能力有限，可以引入DNN生成更复杂的embedding；</li>
<li>传统的MF直接用item/user emb的内积去拟合，也可以再套个DNN；</li>
<li>所谓的“双塔模型”，可以认为就是由此演化而来；</li>
</ul>
</li>
<li>PNN：对DeepCrossing的改进，不同特征的embedding不再是直接stack，而是两两乘积（element-wise），用以获取更多特征交叉信息，还有所谓的内积和外积的区别；</li>
<li>Wide&amp;Deep：可以说是目前影响力最大的模型，google还是NB；<ul>
<li>记忆能力：可简单的理解为从已有的数据中，发现“规则”的能力。比如，如果特征A=male，那么score=1，这种非常直观的规则；如果是简单的LR，那么这个特征A的权重就会很高；深度学习中，由于特征经过了很多处理，又经过各种交叉，这种“规则”的记忆能力反而没有简单模型好；</li>
<li>泛化能力：处理未知数据的能力，或者说，处理稀疏特征向量的能力；</li>
<li>Wide&amp;Deep模型中，Wide部分负责记忆，Deep部分负责泛化；</li>
<li>难点在于，哪些特征进入wide，需要人工选择，抓住业务问题的本质特点：哪些特征是有强“规则”属性的？</li>
</ul>
</li>
<li>Deep&amp;Cross：在wide部分增加更多特征交叉能力（cross网络）；这也比较容易想到，就跟LR-&gt;FM-&gt;FFM的发展过程一样；</li>
<li>DeepFM：用FM替代原来的wide部分；</li>
<li>FNN：用FM参数初始化embedding层权重，加速emb训练收敛速度，也为emb预训练提供了借鉴思路；</li>
<li>NFM：与PNN的结构非常相似，也是得到embedding后，再利用FM的思路，进行特征交叉；</li>
<li>注意力机制：这也是最近很火的议题，关键在于如何抽象用户的兴趣。<ul>
<li>多值/序列类特征，一般的做法是embedding后sum polling，但这其实丢失了很多信息；比如用户收藏的商品id集合作为特征，对这个用户的某次点击，“贡献度”是不同的；更好的做法是加上一个所谓的“激活单元”；</li>
<li>DIN：引入激活单元；其实也是一个小型的神经网络；</li>
<li>DIEN：引入序列信息的处理，兴趣抽取 + 注意力机制；大概就是从用户的历史行为序列中抽取兴趣（GRU，类比RNN和LSTM），得到用户每个行为对应的兴趣状态向量，再叠加注意力机制；</li>
<li>但DIEN一看就感觉有些过于复杂，train/serving估计都很麻烦；后续有资料提到，线上可以将serving和兴趣抽取部分拆分部署，还抽象出所谓的UserInterestCenter，serving时直接取兴趣向量即可；但训练时还是端到端的；</li>
</ul>
</li>
<li>书中还提了一下强化学习，怎么感觉就是模型的实时训练呢？可能训练方法不太一样，不是传统的梯度下降；</li>
</ul>
<p>疑问：理论上，所有特征的emb直接concat后进入全连接层，就可以算是一种特征交叉了，可以学习到各个特征之间更隐式的关联。但DeepFM、NFM之类的做法，就是在concat之前，借鉴FM的思路先做一次特征交叉，可能会更有针对性吧。</p>
<p>纵观各种模型的演化过程，有一个趋势就是越来越贴近业务。为啥DIN/DIEN等模型看着很美好，但其他人都用不起来。因为只有在阿里的电商场景下，才适合这种数据模式，才有足够丰富的数据。没有一个模型能胜任所有业务场景，no silver bullet。</p>
<h3 id="embedding是个好东西">embedding是个好东西</h3>
<p>简称emb，其实就是用一个低维的稠密向量表示一个object，这个object可以是任何东西：词、商品、用户、节点等等。emb表示可以揭露object之间的潜在关系，有那么一点“本体论”的感觉：透过现象看本质。摘录一句话：“基于内容表征的推荐算法往往在推荐的公平性有着更大的优势，它关注的是商品以及用户行为中的内容信息，而非协同过滤算法所关注的用户行为带来的关注度”。</p>
<p>word2vec可以算是emb相关研究的起源，大概就是结合语料，根据当前的词，预测前后n个词的概率。有skip-gram和CBOW两种模式，常用的似乎还是skip-gram。网络很简单，单隐层：<br><img src="/2020/11/05/struggle-to-live/1.png" alt=""></p>
<p>训练过程就是根据滑动窗口，找到所有出现的word pair，输入/输出都是词的one-hot向量，最终训练得到的网络中的权重即是look-up matrix。训练时还有一些优化手段，负采样之类的。<br>不过我有些疑问，这个训练过程似乎没有用到词的前后顺序等信息？跟NLP常见的隐马尔科夫之类的思路不太一样。</p>
<p>广义上来讲，word2vec不只用于NLP，可以为一切“序列”生成emb：</p>
<ul>
<li>item2vec：在推荐领域的应用，利用物品序列生成emb；</li>
<li>graph embedding：为图中的每个节点、边甚至子图生成emb；因为item2vec只能利用序列数据，不能利用item之间的关联（词之间的前后关系？），而graph能引入更多结构化信息；<ul>
<li>DeepWalk：比较基础的方法，随机游走生成序列，再套用word2vec；</li>
<li>node2vec：在deepwalk基础上，通过控制跳转概率，让emb抓取更多的结构性或同质性信息；</li>
<li>EGES：如何融合多种emb（比如根据行为的、根据图谱的），简单说就是构建多个graph，训练多个emb，加权求最终的emb；可以部分解决冷启动问题（即使是新的item，也可以通过一些手段生成emb，比如类目）；</li>
</ul>
</li>
</ul>
<p>graph embedding也是最近非常火热的主题了，但其实相关的研究很早就有了，最近只不过是借着深度学习的东风又火了一把，衍生出了专门的图神经网络（GNN）这一学科。借助深度学习，大规模的、异质图的emb学习，相关研究进展很快。</p>
<p>从工程的角度来说，emb的好处在于：</p>
<ul>
<li>存储方便：相对于图、网络、树等复杂的层次结构，emb就是简单的float array，可以直接存储原始数据，也可以编码后存储；</li>
<li>计算方便：最常见的计算是emb相似度，借助局部敏感性hash等方式，可以做到大数据量的快速召回，业界也有<a href="https://github.com/facebookresearch/faiss" target="_blank" rel="external">Faiss</a>等库；<ul>
<li>局部敏感性hash：欧氏空间中，高维空间中相近的向量，映射到低维空间，也必然是相近的；高维空间中，相距较远的向量，映射到低维，可能相近；将所有的emb映射用同一个随机向量映射到1维空间，然后分桶即可。还可以使用多个hash函数进行映射；</li>
</ul>
</li>
</ul>
<p>emb在推荐系统中的应用：</p>
<ul>
<li>最常见的，用于召回阶段，向量召回的算法和serving架构都已经非常成熟了；</li>
<li>预训练好后，作为其他模型的特征输入；<ul>
<li>emb本身就是十分重要的特征；也可以将emb计算相似度后作为下游的特征；</li>
<li>理论上，端到端训练是最优的，但计算量过大（emb层的参数占整个模型90%以上），所以一般都用预训练方式；</li>
<li>emb一般也比较稳定，更新频率不需要太高；</li>
</ul>
</li>
<li>现在还有所谓的model-bank：其实就是先训练好global的emb，下游的模型直接基于emb+自己的private信息去训练；</li>
</ul>
<h3 id="其他">其他</h3>
<p>如何合理设定优化目标：召回阶段和排序阶段的优化目标应该是一致的，比如都是CTR或都是CVR。尤其是现在很多系统还有多目标优化，比如<a href="https://arxiv.org/pdf/1703.02091.pdf" target="_blank" rel="external">淘宝OCPC的论文</a>中提到，优化目标是广告主的CVR+平台的GMV；阿里的ESMM模型也会同时优化CTR和CVR，称作pCTCVR。</p>
<p>冷启动问题：</p>
<ol>
<li>基于规则：热门或专家知识；</li>
<li>根据有限信息做粗粒度的推荐；<ul>
<li>注册信息、第三方数据、item content；</li>
<li>基于已有的item进行估算：比如EGES、GraphSage等；</li>
</ul>
</li>
<li>主动学习，引导用户输入；</li>
<li>迁移学习：冷启动的问题本质在于数据不足，可以将已有的其他领域的数据或知识，用于本领域的学习；</li>
<li>探索与利用：经典的explore/exploit问题，也被称作多臂老虎机（MAB）问题；<ul>
<li>在传统的MAB问题中，所有老虎机对用户一视同仁，所以不是个性化的；常见解法：e-greedy、tompson sampling、UCB等；</li>
<li>基于上下文的MAB：加上各种超参数，一定程度上解决个性化问题；</li>
<li>传统的MAB解法无法与DNN结合，强化学习算是另外的一种思路；</li>
<li>除了用于冷启动外，也可用于挖掘用户新兴趣、增加结果多样性；</li>
</ul>
</li>
</ol>
<p>评估方式：</p>
<ol>
<li>离线评估：常用的就是AUC（P-R曲线）；</li>
<li>离线仿真：难点在于如何回溯数据，往往要做一些快照机制；</li>
<li>线上AB：直接对比业务指标；</li>
<li>interleaving：与传统AB的盲测不同，将所有的版本都暴露给同一组用户；能测出用户对不同算法的偏好度，但并不是算法的真实表现，需要和AB搭配使用；</li>
</ol>
<p>工程上：</p>
<ul>
<li>深度学习现在基本是TensorFlow一统天下了吧？传统的模型训练还有spark mlib，mahout什么的已经是故纸堆了；</li>
<li>简单的模型用PMML，复杂的DNN模型tf serving；我也基于TF写过java/C++的inference，还挺简单的；</li>
<li>lambda还是主流，kappa还是不成熟；<ul>
<li>题外话：kappa这种架构，有种为了追求完美统一而强行使用的感觉。我们打工人还是讲工程效率的，lambda不完美，但能用，而且搭起来快，就先用着。我天天用着老掉牙的ibatis，我说啥了。。。</li>
</ul>
</li>
</ul>
<h3 id="碎碎念">碎碎念</h3>
<p>推荐系统天生就是一个强data-driven的系统，关键不在于模型和算法的选择，不能拿着锤子找钉子。根源是对数据、对业务的理解。我看到的算法同学，从来没有凭空在那边“炼丹”的，都是深入业务，选择特征/算法，不停验证猜想。有人说推荐系统就是揣摩人心，还有些道理。<br>工程和算法的区别也没那么明显了，往往要算法能力 + 工程能力双肩挑。<br>深度学习依旧热门，但已不是新奇的利器，在慢慢变成基础设施。<br>仔细想想，为啥需要模型这种东西呢？应该是为了泛化。如果我的计算能力足够强大，直接对user ✖️ item算一遍笛卡尔积，是不是也就不用inference了？</p>
<p><a href="https://www.infoq.cn/article/Mfv1ZThAsPvrUSN*C69V" target="_blank" rel="external">CTR预估问题没有“银弹”，比模型结构更重要的是什么？</a></p>
<h2 id="搜索/推荐/广告/投放">搜索/推荐/广告/投放</h2>
<p>这一年多来，接触了不少系统。搜索、推荐、广告、投放这几个词经常在眼前晃悠。在组织架构上，这几个相关的系统也一般是在同一个大部门中。我一直在思考他们之间的区别。</p>
<p>为啥说他们很相似呢？因为他们的本质都是“流量分发”：一个用户进来后，给他展示什么样的结果（item）。广告banner可以是流量分发，微信朋友圈可以是流量分发，酒店中的小卡片也是流量分发。无非就是分发的“目的”不同，导致分发的“手段”也不同，进而系统架构上演化出各自差异。</p>
<p>说实话，流量分发已经不是什么新鲜事了。只不过流量红利的大潮过去后，对分发效率提出了更高的要求。之前看张小龙的演讲：在这个信息过载的时代，你看到的内容大都是被动的，所以流量分发类的系统才这么重要。而你看到的内容，决定了你是一个什么样的人，会有什么样的想法，技术在引导甚至控制人们的生活方式，细思极恐。。。这个时候我们已经不是“人”，而是“流量”。有句话怎么说的，“技术没有感情，资本没有良心”。我们经常忽略了，每个userId背后都是活生生的人。“待人以人”，何其难。</p>
<p>扯远了。</p>
<p>搜索：这里所说的搜索系统，跟传统意义（谷歌/百度）的搜索还不太一样，更像是“一个带query的推荐系统”，比如电商中商品搜索。搜索 vs 推荐可以看做主动获取信息 vs 被动接受信息。当然把网页看做candidate item也未尝不可。技术上有相似之处：倒排、分词、文本相关性（TF-IDF/BM25）之类的。但电商搜索有很多完善的结构化信息，而web搜索面对的是大量半结构化数据，质量参差不齐，所以技术演化方向上也有些不一样，比如PageRank，比如商品搜索可以对item做更多的语义化理解（认知图谱），召回的机制也远比传统意义上的“全文检索”复杂的多。<br>进而，SEO的手段也不同。不过，“砸钱”这个手段还是通用的。。。<br>话说，web搜索也一直在尝试提升结构化程度：<a href="https://schema.org" target="_blank" rel="external">schema.org</a>。</p>
<p>投放：这个词的含义也比较泛，这里指的是电商类体系中“招选搭投”的投放系统。一般来说他应该是推荐、广告等系统的上游，负责将推荐出来的item真正透出。技术上来说，没有固定的形态，有些投放系统包含了展位/预算/疲劳度等广告逻辑，有些纯粹就是一个“通道”（串pipeline的）。门槛不高，相对于搜索/推荐/广告，技术属性更弱一些，每个业务都可以根据自己的需要快速搭建一个，直观体现就是公司内部各种投放系统林立。而且由于他跟业务比较近，往往要吃掉很多的业务逻辑，比如广告类的运营内容投放，和基金类的产品内容投放，逻辑就完全不一样。他的难点也在于对各种业务逻辑的抽象，而不是算法或数据。</p>
<p>真正要对比的，其实是广告和推荐。我一度觉得他们本质上差不多，都是会用到各种CTR模型，各种深度模型也是他们受益最大。但仔细想想，还是区别挺大的，“远远看着是清明上河图，其实画的是北京堵车”。</p>
<p>目标不同：</p>
<ul>
<li>广告系统的目的在于攫取最大的利润（资本为了利润啥事都干得出来），是一种盈利模式；</li>
<li>推荐系统的目的在于改善用户体验，提高留存和黏性，偏产品运营，可以说是一种增长策略；</li>
</ul>
<p>参与者不同：</p>
<ul>
<li>广告系统中是三方博弈：用户、广告主、平台（媒体）；<ul>
<li>某种意义上讲，用户跟广告主是天然对立的，平台就是要在二者间尽量平衡，不能完全偏向用户，又要保证广告主的利益，还要建立长期的流量生态，平衡短期效率和长期繁荣；对于一个健康的商业模式来说，各方的目标应该是一致的；</li>
<li>通俗点说，就是要割韭菜，但是又不能割的太狠。。。</li>
<li>所以才会有eCPM、竞价、GSP等机制（基本都是google发明的）；如果完全按点击率排序，广告主利益受损；如果完全按价格排序，用户利益受损；而eCPM综合考虑了各方利益，不仅包含了经济维度的出价，还包括了广告质量维度和用户契合程度；</li>
<li>根据eCPM的分解决定哪部分是由谁来预估是广告市场各种计费模式（CPC/CPM/CPS等）产生的根本原因；</li>
<li>广告系统还面临另一个问题：如何提升流量的竞争热度，比如冷门的时间段。如果所有的广告主只竞争热门的流量，毫无疑问平台的利益会受损；</li>
</ul>
</li>
<li>推荐系统就很简单了，就是用户和平台两方，用户利益至上；<ul>
<li>所以简单的按照CTR或CVR排序即可；</li>
</ul>
</li>
</ul>
<p>话说，现在各种计费模式，最火的还是OCPX类的吧。感觉就是先试投一段时间，以优化转化为目标训练模型，然后系统自动出价？确实对广告主而言，出价是个难题，出的低了没流量，出的高了又亏钱。决定你的出价的不是你自己，而是和你一起竞争的其他人。。。</p>
<p>从系统架构角度：</p>
<ul>
<li>广告系统要复杂的多，毕竟是涉及到钱的。什么ADX、DMP、DSP、RTB之类的就不说了，单是广告投放的主流程上，就有排期、定向、保量、疲劳度、平滑消费等等复杂的逻辑；<ul>
<li>广告系统会有一些特有的概念：排期、计划、单元、创意等等；</li>
<li>某些投放系统可能会借鉴广告的一些机制，但目的不完全一样；</li>
</ul>
</li>
<li>推荐系统就聚焦的多了，召回 + 几轮排序 + 重排；<ul>
<li>为啥要分多轮排序呢？因为越靠后的轮次，用的特征越多、越实时，模型也越复杂；</li>
</ul>
</li>
<li>广告系统对RT要求是非常高的；而推荐系统相对宽松，甚至可以做成异步，还能结合端智能玩出更多花样；</li>
<li>从分发流程上，二者可能有些相似：本质都是“user + context -&gt; item”，会有召回、模型打分、排序等过程；</li>
</ul>
<p>从数据和算法角度：</p>
<ul>
<li>对推荐系统而言，数据和算法才是核心；而广告系统没有那么强的数据驱动需求，他的“功能”已经是很强的价值；<ul>
<li>或者换一种说法，缺少数据的广告系统还是能run的，而缺少数据的推荐系统基本就是个摆设；</li>
<li>广告系统能拿到的item特征、user特征，都相对较少；毕竟广告中的创意大多是“文案+图片”，跟商品之类的丰富结构化信息完全不可比；</li>
<li>平台没有广告主的数据、不知道这个创意适用于哪类用户，只有广告主自己知道；用户特征也是同理；但最近也有一些广告平台允许广告主自己上传一方数据；</li>
</ul>
</li>
<li>在缺少item特征的情况下，广告系统十分依赖所谓的定向机制，尤其是look-alike，其本质就是“根据item找user：这些item投给哪些人更合适”；类似协同过滤的u2u2i；</li>
<li>推荐系统则是“根据user找item：当前用户推荐哪个item更合适”；</li>
<li>广告系统和推荐系统都会用到CTR预估模型，模型结构可能也类似，但目的完全不同：<ul>
<li>广告系统中的pCTR，是有物理意义的，是真实的点击率预估，参与后续的eCPM计算；</li>
<li>推荐系统中的pCTR，往往只是用于作为分类算法的一个截断阈值；</li>
</ul>
</li>
</ul>
<p>大概就能想到这些吧。其实有时也很难区分，我就见过一些“四不像”的系统。。。</p>
<h2 id="万物皆图">万物皆图</h2>
<p>最近接触了一些图数据库和图神经网络（Graph Neural Network，GNN）相关的东西，总结下。</p>
<h3 id="图数据库">图数据库</h3>
<p>以前，提到图，我想到的还是邻接矩阵、dijkstra、DAG之类的，这是从数据结构、算法的角度去看。但如果从数据本身的角度去看，相比传统的table schema，图其实更适合用来做数据建模，它表征的才是数据之间最本质的“关系”。现在的各种RDBMS，使用join的方式来关联各种数据，其实是非常笨拙的。无论OLTP还是OLAP，无论batch还是stream，join都是一个过于重的操作。那为啥我们还要这样去组织数据呢？换句话说，图数据库为啥不能代替RDBMS？还要啥雪花模型、星型模型啊，反正就是一张图，直接查呗。<br>我猜，很大程度上是因为大多数场景中，RDBMS已经“够用”。TP就不说了，很少做关系类的查询。即使是AP，大多数的查询也并不复杂，而且相关的优化手段已经研究的比较透彻。</p>
<p>图还有一个好处，由于它的组织方式非常自然，有助于发现数据之间的隐藏的关联。这有点像所谓的数据互联，或者数据孤岛问题。我们总是说要将所有的数据聚合到一起，1+1&gt;2，挖掘跨领域的价值。但传统的“数据仓库”、“数据湖”之类的做法，并不能解决这个问题，他们只是把数据“放在一起”，或者说是一个统一的“数据源”。让你想找到数据的时候，可以从中随时找到、访问到，比如新加个特征之类的。而这些数据之间的关联，如何从这些数据的关联中发现信息，进而决策，完全是未知的，还是要依赖专家经验，这是一个“后验”的过程。正如《设计模式》《重构》不能完全解决代码的混乱问题，因为他们只是从技术的角度去看，还必须结合业务角度的领域建模。</p>
<p>图就不太一样，基于图中已有的关系我们可以做一些“推理”（从known facts中发现new facts），尤其是巨量的、异质的图，更容易挖掘出额外信息。比如知识图谱相关的研究，还有最近非常火、号称人工智能3.0的“认知智能图谱”。有人说“没有图谱的AI都是耍流氓”，虽是玩笑，也有些道理。<br>不是说基于数仓就不能做推理，而是数仓的这种数据组织方式天生就不适合，基于数仓搞等于人为给自己加难度。人生苦短啊。。。<br>数据的组织方式真的会影响使用者的思考方式，要不为啥思维导图这么流行呢。另外看看现在多少业务系统设计变成了“mysql表设计”：先有ER图，再有业务逻辑，这就是被RDBMS毒害了啊。</p>
<p>YY下，就像HTAP同时支持行存和列存一样，会不会未来出现一份数据多份存储，同时支持table view和graph view。spark中好像已经有了，但DB中还没见到。</p>
<p>RDBMS有SQL，图数据库也有gremlin，借用一张图：<br><img src="/2020/11/05/struggle-to-live/2.png" alt=""></p>
<p>仔细一想，这图画的还是挺标准的：</p>
<ul>
<li>大多数DB提供的都只是TP的能力；</li>
<li>通过外部的计算框架（spark之类），提供AP能力；</li>
<li>TP和AP共享同样的后端存储；</li>
<li>特殊的地方在于，索引也用外部实现；这种用法也挺常见，典型例子就是用ES实现hbase的二级索引；</li>
</ul>
<p>对于图计算：</p>
<ul>
<li>OLTP：基于遍历方式，一般只访问少量数据，速度快；其实就是多度查询+简单的filter和aggregate；</li>
<li>OLAP：基于BSP模型（或GAS模型），一般要访问全图数据，计算复杂，一般是并行的；</li>
<li>常见计算模式：最短路径、最大连通图、PageRank、数三角形等；</li>
<li>常见推理模式：节点分类、属性填充、相似节点查找、链接预测、社区发现等；</li>
</ul>
<p>有些特殊的计算模式（比如全图遍历），走两种方式都可以。</p>
<p>参考资料：</p>
<p>关于pregel：<br><a href="https://www.jianshu.com/p/388f58539f45" target="_blank" rel="external">分布式图计算模型Pregel详解</a><br><a href="https://cshihong.github.io/2018/05/30/Pregel%EF%BC%88%E5%9B%BE%E8%AE%A1%E7%AE%97%EF%BC%89%E6%8A%80%E6%9C%AF%E5%8E%9F%E7%90%86/" target="_blank" rel="external">Pregel（图计算）技术原理</a><br>pregel模型，有点像design pattern中的visitor模式，又有点MPI的感觉（一种相当古老的分布式编程框架），带有非常明显的MR时代的痕迹。<br>Aggregator跟MR中的counter有点像，提供了一种全局通信、监控和数据查看的机制。</p>
<p>关于GraphX：<br><a href="https://www.cnblogs.com/shishanyuan/p/4747793.html" target="_blank" rel="external">Spark图计算GraphX介绍及实例</a><br>资料有点老，又懒得看官方文档。点分割和边分割的区别很有意思。</p>
<p>基于图的推理<br><a href="https://mp.weixin.qq.com/s/2D2hrnNcTlXsxuWaDcSJRQ" target="_blank" rel="external">https://mp.weixin.qq.com/s/2D2hrnNcTlXsxuWaDcSJRQ</a></p>
<h3 id="GNN">GNN</h3>
<p>对于GNN我只知皮毛，听了些分享而已，但graph embedding是真好用。。。虽然相关的研究在深度学习之前就开始了。</p>
<p>图神经网络（GNN）是跟传统的DNN、CNN等不同的一种特殊网络：</p>
<ul>
<li>网络的结构，包含着重要的信息；</li>
<li>节点和边，有自己的类型和属性；</li>
</ul>
<p>知识图谱也算是一种特殊的GNN。常见的GNN还是大规模的、异质的用户行为图，或者user-item二部图。<br>经典的深度学习方法能够有效的处理原始的输入，比如语音、图片、文本，但对于上面的图结构信息，就不是很有效了。</p>
<p>似乎现在大多数的用法还是生成embedding：</p>
<ul>
<li>无监督学习：相近的点就是有相似的emb；<ul>
<li>DeepWalk就是这种。主要问题是缺乏泛化能力，每当一个新节点出现时，它必须重新训练模型以表示这个节点。因此，不适用于图中节点不断变化的动态图；</li>
</ul>
</li>
<li>有监督学习：其实就是分类。常用算法GraphSage；<ul>
<li>GraphSage 每个节点由其邻域的聚合(aggregation)表示。因此，即使图中出现了在训练过程中没有看到的新节点，它仍然可以用它的邻近节点来恰当地表示。</li>
<li>GraphSage其实可以是无监督也可以是有监督，就是loss function的设计不一样；</li>
</ul>
</li>
</ul>
<p>GNN的训练方法还可以有各种升级：噪声处理、融入时序信息、自监督等等。</p>
<p>从工程上来说，GNN训练的难度在于如何并行。这里要基于一个假设：节点v的k跳邻域，包含生成节点v的k层GNN表示的充分必要信息。据此将整个图拆分并分发任务，典型的share-nothing。</p>
<p>参考资料：</p>
<p><a href="https://zhuanlan.zhihu.com/p/105605774" target="_blank" rel="external">https://zhuanlan.zhihu.com/p/105605774</a><br><a href="https://zhuanlan.zhihu.com/p/62629465" target="_blank" rel="external">https://zhuanlan.zhihu.com/p/62629465</a><br><a href="https://zhuanlan.zhihu.com/p/136521625" target="_blank" rel="external">https://zhuanlan.zhihu.com/p/136521625</a></p>
<h2 id="再谈DDD">再谈DDD</h2>
<p>之前我也写过一些DDD（领域建模）的东西，但写完就忘。。。这套理论还是太抽象了，落地很难。但最近发现，从“重构”的角度去理解DDD，会更简单一些。如果上来就直接看DDD的各种概念，肯定就晕了。</p>
<p>首先要区分：应用架构 vs 系统架构</p>
<ul>
<li>应用架构：你的代码如何设计，如何提升可读性、减慢腐化的速度，如何支持未来的需求，减少维护的成本；<ul>
<li>这里要考虑的：类的设计、层次结构、命名、POM模块的拆分和依赖关系等等；</li>
</ul>
</li>
<li>系统架构：不同系统之间的交互逻辑、职责划分；<ul>
<li>这里要考虑的：请求链路、数据流、异常处理、HA等等；</li>
</ul>
</li>
</ul>
<p>DDD之类所谓的架构，指的都是应用架构。</p>
<p>面条代码大家都见过不少：</p>
<ul>
<li>常规业务系统的分层架构：DAO/Service/Controller；所有的业务逻辑都写在service中，作为“上帝之手”操作各种DO，动辄上千行；如果不是把这个系统从头做起来的人，很难理解全部的逻辑；</li>
<li>业务逻辑、校验逻辑在多个地方重复，好一点的会抽出一个static utils类，差一点的就是每次都实现一遍；改一个逻辑要修改好多地方，还容易遗漏；<ul>
<li>使用utils来封装业务逻辑也不是一个很好的选择；</li>
</ul>
</li>
<li>同理，如果有外部某个依赖变化（比如第三方接口升级、DB表加字段），又是要改很多地方；每次改动还有巨大的回归工作量；</li>
<li>由于大量直接调用DAO和第三方接口，可测试性差，难以写UT；</li>
</ul>
<p>造成这些问题的原因，就是代码缺少层次化的抽象，导致可读性、可维护性都很差。这种时候，我们一般会想到重构，但重构也是要讲究策略的。无论是94年GoF的Design Patterns，99年的Martin Fowler的Refactoring，都是通过一系列的设计模式或范例来降低一些常见的复杂度。但是问题在于，这些书的理念是通过技术手段解决技术问题，但并没有从根本上解决业务的问题。而业界通行的各种“法则”（比如SOLID，比如迪米特法则，比如DRY）又过于缥缈，只是一种“指导思想”。</p>
<p>DDD则是从“业务”的视角，尝试给出的一种比较通用的解决方案，或者说最佳实践，让代码有更强的业务表达能力，更好的沉淀领域知识，解放研发生产力。“DDD最大的好处是将业务语义显现化”，别总想着数据库（对不起我又吐槽了一遍）。<br>虽然DDD也是挺难落地的，可以说这种架构设计就是一个“逆熵”的过程，天生就是反人性的，面条代码更符合人的直觉。</p>
<p>注意：</p>
<ul>
<li>用了DDD，代码会变多，性能未必好（毕竟增加了各种POJO的转换），但是在实际复杂业务场景下，DDD带来的价值是功能性的单一和可测试、可预期，最终反而是整体复杂性的降低；同时也会提升代码可读性，业务逻辑已经融入代码（比如类的拆分、命名，而不是一堆DO的字段和magic value），代码即文档；</li>
<li>DDD并不是这类问题的唯一解，也未必是最优解，只不过很流行。而且DDD本身也是在发展的，DDD（蓝皮书）、IDDD（红皮书）之类的。</li>
<li>DDD本身的实施细节也是有争议的，比如所谓的充血模型和胀血模型；只能说别在意那么多形而上的东西，抓到耗子就是好猫；</li>
</ul>
<p>那么，我有个巨大的面条式service，如何拆分呢？直接说结论的话：</p>
<ul>
<li>用Entity封装单对象的有状态的行为，比如业务校验；</li>
<li>用Domain Primitive封装跟实体无关的无状态计算逻辑；</li>
<li>用Domain Service封装多对象逻辑；</li>
<li>用App Service编排最终的业务流程；本质上，AppService还是“上帝之手”，但是它要操心的业务逻辑少了许多；</li>
</ul>
<p>这里面涉及到很多DDD的术语和思想，直接看结论不是很好懂，不过核心还是基于对业务流程、use case的理解。</p>
<h3 id="DDD的架构主张">DDD的架构主张</h3>
<p>所谓的六边形架构、洋葱架构：</p>
<p><img src="/2020/11/05/struggle-to-live/3.png" alt=""></p>
<ul>
<li>domain layer：包括核心的领域服务和领域模型，大部分的业务逻辑都在这里实现，必须是完整可单测的；</li>
<li>app layer：依赖domain layer的接口或抽象类，做流程的编排；</li>
<li>infra layer：提供基础的第三方服务接入、持久化等；领域模型的持久化，又是另一个很头疼的问题了；</li>
<li>user interface：本质上和infra是一致的；</li>
</ul>
<p>在DDD架构中，能明显看出越外层的代码越稳定，越内层的代码演进越快（因为业务逻辑都在domain layer中），真正体现了领域“驱动”的核心思想。<br>DDD其实就是构建domain layer的一套方法论。</p>
<h3 id="如何构建domain_layer">如何构建domain layer</h3>
<p>这里开始就很抽象了。</p>
<p>战略层面：就是比较虚的概念，知道就行，跟实际的代码没关系：</p>
<ul>
<li>通用语言（UL）：很多时候语言的苍白无力让人无奈。。。其实就是需求分析时、统一思想和用语的过程；<ul>
<li>Domain：需求讨论、分解后，划分出不同的边界和范围；这里又有很多方法了：用例建模法、四色分析法、时间风暴法；</li>
<li>领域的划分是一个很玄学的过程，需要所谓的产品专家、领域专家深度参与；还有所谓的子域、支撑域等概念；</li>
</ul>
</li>
<li>限界上下文（BC）：由domain引出来的概念，每个domain内，有自己的domain model，这些model只有在这个域内才有意义；<ul>
<li>比如电商体系中，用户这个概念，在权限管理领域、在交易领域、在支付领域，需要的属性、行为都不一样；很可能是每个领域都有自己的用户model；</li>
<li>怎么解这个问题？常见的方式：共享内核（一个巨大的model，包含所有需要的字段）；依赖关系（通过继承的方式增加一些字段）；防腐层（做一些适配和转换）；</li>
<li>BC是一个抽象的概念，并不会落到代码中的具体某个类；即所谓的“战略建模”概念；</li>
<li>BC通常可以用做微服务的划分标准；</li>
<li>谁来做上下文之间的mapping？一般是app层；</li>
</ul>
</li>
</ul>
<p>战术层面：教你如何写代码</p>
<ul>
<li>Entity：有唯一标识的、有状态的、有行为的POJO<ul>
<li>Entity是从业务流程中抽象出来，并不是凭空而来的；比如订单（Order）可以作为一个Entity；它的行为一般是改变自己的状态，其实就是对业务规则的封装；</li>
<li>和Data Object有关系，但并不等同；</li>
<li>生命周期仅存在于内存中，设计时不需要考虑序列化和持久化；</li>
</ul>
</li>
<li>Value Object：不可变对象，无唯一标识的POJO<ul>
<li>典型的比如各种DTO；</li>
<li>一般不带行为；</li>
</ul>
</li>
<li>Domain Primitive：一种特殊的Value Object，可以带一些无状态、无副作用的行为（封装业务规则）；<ul>
<li>并不是来自最初的DDD理论，而是后来者的创造；</li>
<li>典型的DP：用户的手机号，是用一个String来存，还是一个PhoneNumber类？手机号是有特定的校验逻辑的；</li>
<li>DP是有一点反直觉的，还是手机号校验的例子，我是建一个PhoneNumber类，然后写个isValid方法？还是写个utils类，写个isValidPhoneNumber方法？从OO的角度看来，前者是更佳的；</li>
</ul>
</li>
<li>Aggregate：有些时候，多个实体、值对象放在一起，才能完整表示一个业务概念；比如订单中有主订单与子订单概念，主/子订单对应不同的Entity对象，但是他们必须是一起出现、一起操作。<ul>
<li>跟UML的聚合关系有些像，是“部分”和“整体”的关系，“部分”不能脱离“整体”单独存在；</li>
<li>聚合也是为了封装业务逻辑；比如子订单价格变化时，主订单的价格也要变化；</li>
<li>同样也都是内存操作；但聚合是用于持久化的最小单元；由此引出所谓的聚合根的概念：比如订单ID；</li>
<li>在聚合边界之内，对象之间可以直接引用；在聚合边界之外，只能通过聚合根引用；</li>
<li>代码中往往并不会有所谓的“聚合类”，而是某个Entity类就可以充当聚合（也就是所谓的聚合根）</li>
</ul>
</li>
<li>Repository：负责聚合的持久化；<ul>
<li>负责和具体的持久化实现解耦，所谓它的方法名一般不会用select/insert之类，而是会用find/add等；</li>
</ul>
</li>
<li>Domain Service：封装同一个领域内涉及多个model的操作或动作，避免暴露到上层；<ul>
<li>识别领域服务，主要看它是否满足如下特点：服务执行的操作代表了一个领域概念，这个领域概念无法自然地隶属于一个实体或者值对象；被执行的操作涉及领域中的其他对象；操作是无状态的；</li>
</ul>
</li>
<li>防腐层：一个比较特殊的概念，比如在接入各种第三方服务时，做一层适配；</li>
</ul>
<p>概念就是这些，但想写出好的DDD风格的代码，还是挺难的。只能说，在各种代码屎山里多摸爬滚打，慢慢就能体会DDD可贵之处了。。。</p>
<h3 id="疑点">疑点</h3>
<p>所谓的CQRS，我感觉有点扯淡。之所以出现这种模式，根本原因在于领域模型并不适合用于查询。比如我只想查主订单的信息，但领域模型只提供getById这种方式，查出来的是一个聚合，强行带了很多我不需要的信息，效率会很差；于是提出了所谓的命令和查询分离模式。至于后来所谓的事件驱动、事件溯源，还引入了所谓的domain event，都是附带的了。<br>所以也有人认为，所有的查询都应该绕过domain层，直接和数据库打交道。<br>事件驱动本身就是常见的模式，跟DDD关系不大。正如系统设计中MQ是常见的解耦方式。</p>
<p>Repository的设计，是个大难题。由于Entity在设计时并不会考虑持久化，全是内存操作，那么我在真实写入DB时，到底有哪些数据是变化的，哪些没变？比如我的Aggregate Root（DB中对应表A）中有一个<code>List&lt;OtherEntity&gt;</code>（DB中对应表B），更新的时候表A肯定是要update的，但表B可能要update或insert，如何区分？我见过一些做法是在repo层做snapshot + diff，感觉有些重，也不自然。简单粗暴的方式，就是将表B中所有相关的记录删除+重新insert，不过效率不高。</p>
<h2 id="云原生微吐槽">云原生微吐槽</h2>
<p>云原生（cloud native）这个词，不知不觉又火了。我这么喜欢凑技术热闹的人，当然得了解下啊。</p>
<p>如果望文生义的话，所谓的云原生，感觉就是后端架构天然适合云，不用再搞什么“上云”、“登月”之类的。如果说的具体一点，什么样的应用才是云原生的？那就要搬出CNCF的<a href="https://12factor.net/zh_cn/" target="_blank" rel="external">十二要素</a>了。但如果去看其技术实现，似乎和现在没啥区别啊，不就是docker、mesh、微服务、k8s等等。</p>
<p>所以说，我感觉这就是一个营销词汇。但云计算领域怎么这么多营销词汇啊？这明明是一个强技术驱动的领域。一般只有同质化太严重的时候，才需要去包装各种概念来忽悠外行。</p>
<p>列举一些概念：</p>
<ul>
<li>微服务：解决了单体应用的问题，不用关心其他人的模块，独立发布；<ul>
<li>但带来了服务调用/服务治理/分布式事务/可观测性（logging/metric/trace）等问题；</li>
<li>如何拆分微服务？连带着DDD也又火了一把；</li>
</ul>
</li>
<li>mesh：进一步将通信层拆分出去，服务调用、流量管理等问题可以不用关心了，都走sidecar；<ul>
<li>还有所谓的db mesh：访问db都不直连了，也是走sidecar；</li>
</ul>
</li>
<li>serverless：更模糊的一个词。理念就是用户不用再关心backend，直接提供业务实现就好；<ul>
<li>无论底层是虚拟机or容器，用户都是不可见的；</li>
<li>FaaS只是serverless的一个特例；现在大多数所谓的serverless实现还是基于容器的；</li>
</ul>
</li>
<li>BaaS：处于PaaS和SaaS之间，将后端诸如消息服务、认证服务、存储服务等能力以SDK和API的形式提供；<ul>
<li>感觉就是另一种形式的中间件么，将能力下沉到平台层，而不是以jar包的形式集成在业务代码中；</li>
<li>想想每次升级中间件版本时的蛋疼；</li>
<li>典型的BaaS能力：cache和消息队列；</li>
</ul>
</li>
<li>CaaS：以容器为核心，介于IaaS和PaaS之间；但说到底不就是k8s么；</li>
<li>DaaS：数据即服务，什么鬼啊？我看一些架构图里，把spark/flink之类算作DaaS，我一个搞数据平台出身的人很无语。。。</li>
</ul>
<p>但纵观各种架构的演进过程，核心理念是不变的：让开发人员专注核心业务逻辑，快速演进，不要关心各种基础设施；将之前各种在应用层考虑的东西，落到框架层去解决。“与其进行顶层设计一揽子的解决方案，不如相信演进的力量。好的架构不是能大包大揽的一次性解决你所有的问题，而是给了你这种可能性：低成本的演进、试错，去达到一个理想的状态。”</p>
<p>一步步的，你只要关心自己的业务逻辑，越来越接近无状态应用。<br>甚至，业务逻辑都不用太关心了，各种中台SDK已经帮你包装好了，关心可变的部分就好。<br>会不会发展到最后，没有所谓的甲方/乙方，程序开发完全变成了表意的：上帝说“要有光”，于是有了光。这才是抽象的极致嘛。</p>
<p>这就是计算机科学：整合再整合，抽象再抽象，然后你的东西就过时了。。。<br>公司内部也经常出现这种：你做了个系统，我就再做个系统把你包掉。。。这是我的抽象层次更高呢？还是只换个皮呢？</p>
<p>所谓的创新大多都只是这种“整合”，真正能做出突破性贡献的人（Geoffrey Hilton这种）还是太少了。</p>
<h2 id="Building_Blocks">Building Blocks</h2>
<p>这个词还是从《Concurrency in Practice》中学来的，一个拗口的翻译是“构建块”，其实就是一些可复用的代码/做法/逻辑。</p>
<p>最近也看了不少业务系统，发现从技术的角度看，有些共性：</p>
<ul>
<li>一个任务执行框架：简单点就是pipeline，复杂点就是DAG；<ul>
<li>这里的DAG还不同于大数据语境中的DAG，就是个单机的执行框架；</li>
</ul>
</li>
<li>一个分布式的调度框架：简单点就是分布式crontab，这种中间件也不少；<ul>
<li>大多数业务系统不会去做复杂的分布式协调逻辑，虽然机器动辄几千台，但都是“平等”的/无状态的；</li>
<li>不同于大数据语境中复杂的依赖调度；</li>
</ul>
</li>
<li>可能有缓存机制：比如LoadingCache+Redis的多级缓存；</li>
<li>可能有规则引擎：比如做实时的策略干预，做的复杂点还可以基于rete network；</li>
<li>流程编排引擎：所谓编排，就是用配置的方式“串流程”，配置代替硬编码；<ul>
<li>编排这个词，有些被滥用了；</li>
</ul>
</li>
<li>事件总线；</li>
<li>状态机；</li>
<li>复杂点的，还可能有自己的DSL；</li>
</ul>
<p>这些东西吧，可能是middleware/framework/library，但感觉有些“共性”，我只能统一称之为building block。<br>往往各种building block拼拼凑凑，再叠加一些业务逻辑（DDD），就能搭出一个系统了。大多数业务类系统很难超出这个范畴。</p>
<p>更进一步，如果从能力的角度去解析这些系统：</p>
<ul>
<li>产品能力：后台配置/管理/干预/分析；</li>
<li>系统能力：对外提供什么样的服务；请求流程是什么样的；</li>
<li>数据能力：数据pipeline是什么样的；消费哪些数据，提供哪些数据；</li>
<li>监控/容灾/运维等非业务能力；</li>
</ul>
<p>把这些能力抽象出一个个小方块，再拼拼凑凑，就能画出一个看起来很复杂的架构图了。<br>大多数业务系统的架构也就是这么来的。</p>
<p>再进一步，打工人的PPT抽出一个个小方块，拼拼凑凑，就变成老板PPT。。。<br>Hooray for the layers of abstraction!</p>
<h2 id="数据的价值">数据的价值</h2>
<p>老生常谈了。data-&gt;information-&gt;knowledge-&gt;wisdom这条路，也不是新鲜概念了，但落地上还是很多困难。业务数据应用的最后一公里，到底有多长？如何解决数据 -&gt; 应用的鸿沟？<br>有人说用了数据库、用了一些配置系统便是“数据驱动”了，我感觉我们不在同一个频道。。。</p>
<p>最近我发现公司内部有一个神奇的title：“数据解决方案架构师”，于是了解了下。这是一个跨领域的职位，“利用底层数据、工具与平台，通过数据分析和洞察找到业务上可运营可优化的机会点，以数据模型、标签、产品、SOP等方式提供端到端的数据解决方案，实现因我而带来的数据增量价值”。看起来，还是要贴着业务打，对业务流程的理解、对业务痛点的分析和拆解才是根本，数据/工具/平台只是手段而已。本质还是运营，但是更懂数据，能发现新的运营机会和更高效的运营手段、提升决策效率，典型的，比如growth hacker、比如SEO。<br>就像“互联网+”一样，数据往往提供的也是一个“+”的能力。</p>
<p>一般工作流程：业务理解-&gt;目标分解-&gt;设计方案-&gt;方案验证-&gt;上线评估-&gt;产品化沉淀。<br>业务效果 + 技术/产品沉淀，两手都要抓。大多数业务系统也都是这么个套路。<strong>说的极端点，没有沉淀出场景/解决方案/数据资产的系统，都不合格。</strong></p>
<p>如何定义一个数据运营问题？</p>
<ul>
<li>目标：可量化、可拆解，将目标分解为各个因子分别去提升；</li>
<li>策略：可描述、可执行；</li>
<li>效果：可量化、可观察，建立过程监控指标；</li>
</ul>
<p>从某种角度来说，和BI有些像。BI到底是一个平台型的团队，还是垂直的团队？是否要为业务结果负责？话说回来，现在也没啥纯粹的BI团队了，大多数都是数据+算法的混合角色。</p>
<p>回到老本行，从平台开发的角度来说，如何帮助数据发挥价值？</p>
<ul>
<li>数据的标准化：或者说，建设数据底盘。<ul>
<li>真正有价值的一定是数据本身，而不是数据处理技术；</li>
</ul>
</li>
<li>多样化的数据组织和探索形式：单一的schema设计往往是不能满足需求的；<ul>
<li>比如上文中提到的图；</li>
<li>需要长期坚持并且不断根据业务形态调整，形成一套数据构架的方法论；</li>
<li>很多尝试性的工作，不确定性高；很苦逼，正如以前的人工特征工程；</li>
</ul>
</li>
<li>数据的实时化：这是比较容易想到的；</li>
<li>稳定的数据资产服务；</li>
<li>合理的产品化流程：比如很多系统都有异动检测，但不应只是给出建议，更要有方便的手段让运营去验证，直接把AB流程、决策流程集成到产品中；</li>
</ul>
<p>这些都是我能提供的技术/产品能力，再向下拆解才会是spark、flink等等。<br>基于此，无论是运营、解决方案架构师、还是BI，才有可能去做更多的数据探索和价值挖掘。</p>
<h2 id="各种读后感">各种读后感</h2>
<p><a href="https://mp.weixin.qq.com/s/b6XHZq6C0ZWOcDrysjMoog" target="_blank" rel="external">RALM: 实时Look-alike算法在微信看一看中的应用</a><br>特征工程对数据做了一些抽象，有相同特征的item就会被认为是“一样的”。抽象就不可避免会丢失一些信息，毕竟选择太多特征会破坏泛化性能。但对此也有一些争议：这篇文章认为，这些损失可能导致模型过热。文中提到“整个model对item后验数据十分依赖，导致推荐结果趋向于CTR表现好或者PV表现好的item”，我猜测是因为他们将一些统计性的指标也作为了特征，比如历史CTR。如果只使用item本身的“固有属性”，理论上似乎不应该有长尾？毕竟CTR才是命脉，再烂的内容，哪怕是标题党，只要有人点击都会推。换成用户感兴趣的冷门小众内容，也许CTR不会降，但要冒风险。所以文中用的措辞是“在保证CTR的前提下，加强长尾内容的分发”。不过将look-alike引入推荐系统还是一个很好的思路，感觉可以用来部分解决冷启动问题。“如果能完整的用受众用户的行为来计算item的特征，可以说是最完整的item历史特征的建模”。</p>
<ul>
<li>先尝试性曝光，积累几百级别的种子用户；</li>
<li>用这些种子用户的feature，代替item的feature，来和其他用户做look-alike；本质还是找相似用户；</li>
</ul>
<p><a href="https://zhuanlan.zhihu.com/p/186320100" target="_blank" rel="external">阿里定向广告最新突破：面向下一代的粗排排序系统COLD</a><br>所谓“我们重新思考了模型和算力的关系，从两者联合设计优化的视角出发”，言下之意：“现在算力已经足够强了，再加上一些优化，即使是粗排，也可以上复杂模型”。所以这篇文章里所谓的粗排，和精排已经差不多了。<br>对于一些历史方式、问题的总结还不错。<br>到底需要多少轮排序？没有定数。。。</p>
<p><a href="https://zhuanlan.zhihu.com/p/97821040" target="_blank" rel="external">谈谈推荐场景中召回模型的演化过程</a><br><a href="https://zhuanlan.zhihu.com/p/100019681" target="_blank" rel="external">推荐系统技术演进趋势：从召回到排序再到重排</a><br>很赞，总结的很全。<br>关于召回：“总体而言，召回环节的有监督模型化以及一切Embedding化，这是两个相辅相成的总体发展趋势”。<br>知识图谱是所谓的图模型召回的特例，优点是可解释性。</p>
<ul>
<li>知识图谱：静态知识；图模型：用户行为 + 物品；</li>
<li>知识表示：显式表示（schema或SPO三元组）和隐式表示（embedding）；</li>
</ul>
<p><a href="https://mp.weixin.qq.com/s/zoL-bwZ-DLyopfXSYn9-Ig" target="_blank" rel="external">多业务融合场景下的推荐算法实践与思考</a><br>不够高大上，但确实是一线的实践</p>
<ul>
<li>最重要的是理解业务，无论是洗数据、搭模型、还是简单的应用一些策略，这都是前提；</li>
<li>文中没有介绍什么NB的模型，而是基于业务理解，提出了很多策略上的优化，但确实有效；</li>
<li>推荐热点问题，文中没有在召回阶段解决，而是在重排阶段解决（热门的往后排），感觉这种方法也许有一些效果，但有限；</li>
</ul>
<p><a href="https://mp.weixin.qq.com/s/Xs-sSZFx9FWPGtg_X6gU4g" target="_blank" rel="external">商业DMP数据管理平台的架构与实践</a><br>感觉有点做歪了，糅合了很多特征服务平台的事情。<br>圈人、洞察、再营销，都是基础操作。</p>
<p><a href="https://mp.weixin.qq.com/s/AFKR_f3KvWdzMeIZR7XnVA" target="_blank" rel="external">从流量到增长，营销产品有何趋势</a><br>所谓的第一方DMP：“客户可以基于自有数据去分析洞察自有人群，也可以基于腾讯广告开放的数据能力，用自己的算法定制人群标签，并且通过腾讯广告DMP在腾讯的广告系统里进行定向投放”。</p>
<p><a href="https://zhuanlan.zhihu.com/p/56784484" target="_blank" rel="external">广告中oCPX到底是如何进行优化的？</a><br>facebook的oCPM，是以点击去竞价，但结算还是以CPM方式。<br>而淘宝的oCPC，就是以点击结算（当然还有GSP机制），而且优化目的在保证或提高广告主ROI的情况下，最大化平台的GMV。<br>简单点说，就是对转化概率高的优质流量，提高出价；对比较差的流量，降低出价；出价的逻辑受 预测点击率/真实点击率 的比值影响。</p>
<h2 id="念念不忘，必有回响">念念不忘，必有回响</h2>
<p>写到最后了，还是要走波心的。</p>
<p>“Struggle to live”，直译的话是“挣扎求生”么？我更愿称为“用力活着”。当你浑浑噩噩的过了一周、一个月，甚至几个月，总有一天会突然醒悟，后悔为什么要这样浪费生命，被巨大的自责淹没。<br>无论是好是坏，至少别给自己留下后悔的机会。很多事情事后看来也许是个错误，但如果能回到当时的“上下文”，还是会做出同样的选择。</p>
<p>一年之前，我肯定难以想象自己现在做的事：推荐、图、算法、embedding，每天面对的都是巨大而无形的问题。以前我做事，心中是有一张大图的，我知道应该做成什么样子，跟现实的差距在哪里，不光自己笃定，还能向其他人输出，也从来不怕和别人battle。现在么，直白的说，没那么笃定了，但愿意去尝试和摸索。<br>说到底，哪有那么多确定的事呢？以前的笃定更多是因为事情简单。</p>
<p>我大概就是搞平台的人里，业务sence最好的；也是业务团队里，平台积累最深的。。。我以前说我是造铲子的，结果现在越来越接近直接挖坑的。。。技术的深度和广度很重要，但像NBA赛场一样，如何将天赋兑现，才是最重要的。<br>但不管怎样，数据的“主线”还在，无论碰到什么样的人和事，围绕主线的思考也从来没有停止。最怕的是被环境裹挟，丧失了独立思考的能力。好在，我还能写些文字。</p>
<p>题外话，在大公司里见识了太多的人，我已经学会如何区分真假大佬：真正的大佬NB在于他们的思考能力，甚至不是落地的能力。看清楚前因后果，看清楚可能的问题，先人一步。不管这种思考的深度是来自于天赋异禀，还是过往经验。做个比喻的话，有点福尔摩斯的哥哥迈克洛夫特的感觉。<br>这种能力对外的表现：话不需要多，但说的都是一针见血，而且再复杂的道理也能用通俗的语言解释出来，让所有人听懂。</p>
<p>为啥要学会这种区分呢？为了避免踩坑。见过一些人，语速很快，各种专业名词信手拈来，不管自己说的对不对，不管别人懂不懂，总是先争取发声的机会，给人一种貌似很NB的错觉。结果，要么就是你听不懂，晕晕乎乎；要么就是听懂了，但仔细一想，毫无逻辑。</p>
<p>唠唠叨叨扯了这么多，都是有感而发，但其实人类的悲欢并不相通，比如马老师在愁着如何花钱，而我在愁着如何挣钱。各自努力，各安天命吧。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>俗务缠身，难得有个僻静的地方可以写些东西。</p>
]]>
    
    </summary>
    
      <category term="推荐" scheme="http://jxy.me/tags/%E6%8E%A8%E8%8D%90/"/>
    
      <category term="算法" scheme="http://jxy.me/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[人间好时节]]></title>
    <link href="http://jxy.me/2019/08/01/maybe-a-good-time/"/>
    <id>http://jxy.me/2019/08/01/maybe-a-good-time/</id>
    <published>2019-08-01T08:37:57.000Z</published>
    <updated>2019-08-11T14:54:36.000Z</updated>
    <content type="html"><![CDATA[<center>“若无闲事挂心头，便是人间好时节。”</center>

<a id="more"></a>
<hr>

<p>这是句颇为自嘲的话，往往只有忙碌才能让人心安。或者说，忙碌才能让人忘却不安。<br>最近各种动荡，事情变来变去，理论上我应该是最头疼的才对，不知为何却奇妙的非常平静，也许这就是所谓的inner peace？<br>随着各种槽点逐渐积累，又可以“敷衍”出一篇文章了。</p>
<h1 id="啥是中台？">啥是中台？</h1>
<p>“中台”这个东西，颇为玄妙。就像所谓的teenage sex：每个人都在谈论，但没人做过；每个人都声称自己做过，而且以为其他人都做过。于是我也不能免俗啊，总结下自己的理解。</p>
<p>我之前做数据平台，对这块还算了解。对平台而言，最重要的是三个字，“标准化”。我们提供通用的、标准化的能力：计算、存储、检索、开发IDE、SDK等，用户可以在此基础上组装自己的业务逻辑，典型的one fits all。平台提供的能力没有任何业务含义，而且往往有一套通用的“业界标准”，照猫画虎去实现就可以了，不同公司做出来骨架都差不多，血肉会有区别。总的来说比较偏技术，边界也比较清晰。</p>
<p>但平台也有自己的缺点：</p>
<ul>
<li>入门的门槛较高。业务方如果真要用这一堆积木搭出自己的系统，需要很多前置知识。SQL你得会吧，调优也得懂吧，跑的慢怎么办，日志怎么看，数据如何治理；</li>
<li>对业务方而言比较繁琐，而且要遵守很多规范。作为平台方，我们希望用户不要逾矩，按我们的规范（SQL规范、表规范、指标规范、资源使用规范）来做事，但无论是硬性的产品限制，还是软性的规章制度，总是有些“不听话”的用户。个别用户甚至破解了我们的REST API，私自在系统里调用。。。所以我们一直在和用户“斗智斗勇”。这种事情吧，不好说谁对谁错。我作为平台方要维护长远的稳定性和完整性，但也能理解他们的苦衷；</li>
<li>平台对新需求反应一般比较慢，而且会拒绝掉很多带业务属性的、非通用化的需求，这也是用户经常吐槽的点，“怎么你们做需求这么慢”。对平台而言，最重要的是标准化，其次是稳定和效率，其他的都可以一定程度上牺牲；</li>
<li>最致命的：业务方搭出的东西，很难复用。下次有另一个业务方需要时，可能还要重新搭一遍，累，而且烦。大多数业务方的数据需求是类似的：用户标签（任何商业模式，只要想做运营，你都要了解自己的用户）、人群分析/透视、渠道归因分析等等，这其中很多模式可以固定下来并复用；</li>
</ul>
<p>各种数据应用（GrowingIO/GA之类）会克服平台的一些问题，比如抽象出所谓的“套件”/“解决方案”来实现场景的复用、屏蔽平台的各种细节降低门槛达到开箱即用（终于不用关心SQL调优了）。但由于产品形态相对固定，更像是一杆到底的大包大揽，没有提供可扩展的API，你只能使用它提供的功能，而很难根据自己的业务特性去定制。或者说，这些产品是面向老板or运营的，而不是面向开发的。</p>
<p>如何弥补平台和应用之间的gap，就是中台的使命了。其实这也不是什么新鲜东西，可能很多公司都有类似的做法，只不过没有抽象出这一层。比如一个开发做类似的功能会在自己的多个应用之间copy and paste，时间长了慢慢就会形成自己的库，更进一步可能形成通用的SDK，这就有点中台的意味了。所以说计算机科学就是一门关于抽象的科学，hooray for the layers of abstraction。</p>
<p>中台很难是一个单独的系统或者是一个单独的SDK（正如数据平台也不等于hadoop），它应该是多个系统、多个方法论、多个规范的总和。所以很难说它“是什么”，只能描述它“应该具有的特征”：</p>
<ul>
<li>面向业务场景，而且这些场景是可复用的，相对平台而言更“具象化”，“表达能力”/“scope”更小。这是核心特征，因为中台的目的就是简化各个业务方的开发代价，从其中抽象出共性并沉淀下来。如果业务场景不够复杂，别搞中台，只会适得其反。典型的复杂业务场景就是电商中的各种玩法，这也是为什么中台战略会是阿里首先提出来的原因；</li>
<li>提供足够丰富的API/扩展点，让业务开发可以根据自己的需求自由扩展、组合。这也是中台和平台的一个显著不同。平台会提供API，但这些API往往是固定的，如果不满足你的需求，你只能提需求要新的API；而中台提供的API，必须留下业务方自由扩展的空间。比如退货流程，电商中台会提供默认的、标准的退货流程，但如果你的业务场景特殊，完全可以自己覆盖默认的实现。所以说，中台的目标用户是业务开发人员。</li>
<li>中台未必需要有“界面”，API才是核心；</li>
<li>和平台不同，中台的边界很模糊。很多东西丢给业务方自己去做似乎可以，沉到中台来做好像也行。越接近上层、离业务越近，边界就越难划分，任何系统都是如此。很多时候也不用太纠结。正如中间件层越做越厚，“向上”侵蚀了很多业务开发的空间一样。</li>
</ul>
<p>所以不管是业务中台、数据中台、算法中台，做好的前提是你要去了解你的业务方，了解之后才可能抽象的足够好。想想从他们的角度看来，需要哪些能力，如何才能更省力，比业务更懂业务。很多时候，业务方自己会有思维惯性和死角，也不知道更好的做法是什么，需要我们去push他。如何找业务方聊，多拉一些人背书，推广自己的产品，这又是一门艺术了。</p>
<p>写这些文字的过程中，总觉得好像模模糊糊摸到了某些更“真理”的东西，却又觉得有些虚无缥缈。大概这就是玄学研究多了的后果吧。。。</p>
<p>参考资料：</p>
<p><a href="https://www.infoq.cn/article/J0peu2bQ5jq_6jhBzSOe" target="_blank" rel="external">数据中台不是技术平台，没有标准架构</a></p>
<p><a href="https://www.infoq.cn/article/4PXxXJ*ZOmPVlAtB2Ttb" target="_blank" rel="external">数据中台已成下一风口，它会颠覆数据工程师的工作吗？</a></p>
<h1 id="技术平民化">技术平民化</h1>
<p>入职蚂蚁后，终于见到了PAI的真容。简单训练了一个评分卡模型，用了下分箱、WOE变换、特征分析等组件，真tm方便，我这种算法盲也能很快上手。我也不要求自己深究各个算法的原理和调参，只要会用就很满足了。</p>
<p>这让我想到，近年来的各种技术热点，什么“AI”、“大数据”、“云”，随着平台化、产品化的推进，门槛真的是越降越低。“算法能力”、“数据能力”慢慢会变成每个工程师的标配，而不应该只依赖于少数“专家”。忘了在哪里看到的，蚂蚁CTO阿玺去google挖人，希望能招到优秀的大数据专家，结果人家很诧异，“我们没有这种职位啊”。因为基础设施已经足够好用，人人都可以发挥自己的想象力，而不需要被职位所限。</p>
<p>早些年，你懂个lucene都能横着走。。。当时也有一种论调，“我就把一个东西研究透（这个东西可以是oracle、lucene、hadoop、linux之类），死磕，只要搞明白了，我就牛逼”。不能说这种想法错吧，只能说在当今的时代，做到这点越来越难了。纯粹的技术研究、技术创新，只属于一小撮高端的人，对个人素质的要求越来越高，圈子也会越来越小。大多数人所做的，都是“应用开发”；稍微深入一点的，可能是“平台开发”；真正到kernel那种级别的，凤毛麟角。这不光是因为系统越来越复杂，更多是由于大环境“不需要”这么多高端的人。</p>
<p>想想也对，如果底层系统真的足够完善，留少数人维护就好了，资本家都是很现实的，靠梦想驱动的毕竟还是少数。就像AI一样，如果未来真的替代了人工，可能导致很多人失业，但这个潮流谁也无法阻挡。历史上珍妮机的发明、几次工业革命的进程，都证明了这个道理。<br>这些年来IT从业者越来越多，个人素质却越来越堪忧，培训几个月就能上岗，也是同样的道理，因为“不需要”。</p>
<p>作为民工，除了被潮流裹挟，我能做点啥嘞，我也一直在思考。好在目前各种技术壁垒还没有那么高，在平民化的过程中，还有很多机会可以挖掘。</p>
<ul>
<li>首先别妄图对抗潮流，想自己闭门造个什么极其牛逼的东西，然后一朝成名天下知。这是很多技术人的通病，现实中的关键往往不在于技术如何牛逼，而在于如何把技术推销出去，一个好看的界面可能比一个O(logN)的算法重要。我也一直觉得，产品化、平台化的重要性被很多人忽视，这是一个典型的double yes问题，技术和产品不能有任意一个短板，两手都要硬。</li>
<li>其次摆正自己的定位，自己适合做什么，想清楚。我觉得我就不适合做最底层的东西，虽然我很感兴趣，也愿意去了解，但比如说想到要研究一辈子数据库，天天读论文，就觉得很无趣。很多理论的东西说的极端点就是空想，<strong>我还是希望看到自己的东西能影响尽可能多的人</strong>。以前做平台，最大的成就感不是把平台搭出来，而是有很多人来用。我也不太适合去直接面向业务，这种事需要的是商业sense和决断力，我好像从来也没展露出这方面的天赋。我更适合的应该还是造铲子，然后卖给其他淘金人。这么说来，在潮流中我扮演的应该不是弄潮儿，而是推波助澜的投机者。。。吃不到肉喝口汤也行对吧。</li>
</ul>
<p>但话又说回来，铲子也没那么好造，作为承上启下的纽带，需要对上下游都有了解。正如开头那个PAI的例子：不要求深究各个算法，但要理解基本原理，要会用。</p>
<h1 id="读后感">读后感</h1>
<p>我喜欢看到什么有趣的就先记下来，所以有些文章已经比较久了。</p>
<p><a href="https://yq.aliyun.com/articles/621200" target="_blank" rel="external">java脚本引擎的设计原理浅析</a><br>前段时间一直在研究规则引擎，这篇文章写的非常好，流程引擎/规则引擎/脚本引擎，还是不太一样</p>
<p><a href="https://www.jianshu.com/p/cd3557b1a474" target="_blank" rel="external">RxJava2.0——从放弃到入门</a><br>似乎android开发中使用rxjava比较多，我觉得其核心价值在于简化异步调用，使程序保持简洁（还记得js中的callback hell么）。异步 + 链式调用，类似js中的promise，但java世界还没有发展出async、await之类的东西。注意异步未必等于并发，协程不也是异步么。<br>Reactive的本质在于事件驱动，类似eventbus，观察者模式也是同样的道理，js的eventloop也是，yarn中的状态机也是。netty中的reactor模式也是同样的原理（有叫做reactive的，也有叫做reactor的，其实都是一个东西）。</p>
<p><a href="https://infoq.cn/article/kylin-apache-in-meituan-olap-scenarios-practice" target="_blank" rel="external">Apache Kylin 在美团数十亿数据 OLAP 场景下的实践</a><br>关键在于：1. 构建cube的过程如何优化？分层策略；2. cube如何存储，hbase rowkey编码方式；3. SQL查询如何改写成hbase查询；</p>
<p><a href="https://tech.meituan.com/2016/11/18/disruptor.html" target="_blank" rel="external">高性能队列——Disruptor</a><br>关键词：RingBuffer、无锁（大量利用CAS和volatile）、cache friendly<br>对比ArrayBlockingQueue来看</p>
<p><a href="https://mp.weixin.qq.com/s/MGHFGWsqFdQGw83AYOHEaw" target="_blank" rel="external">携程金融大数据风控算法实践</a>：A卡原来是这么回事</p>
<p><a href="https://www.infoq.cn/article/69vGmFcrTwSC_w7RWhwz" target="_blank" rel="external">为什么说流处理即未来？</a><br>很激进的想法，使用流处理的state完全替代db，并在此之上实现事务特性<br>中间事务的那一段描述很简单，但直觉上感觉肯定有问题，不可能这么简单。。。<br>这种东西也不能称作强一致性，必然会被外界看到不一致的中间状态<br>而且啊，流处理的retraction代价这种场景不能不考虑，也许远比带来的好处多，对比db的rollback</p>
<p><a href="https://zh.wikipedia.org/zh-hans/%E9%80%9A%E7%94%A8%E5%94%AF%E4%B8%80%E8%AF%86%E5%88%AB%E7%A0%81" target="_blank" rel="external">通用唯一识别码</a><br><a href="https://tech.meituan.com/2017/04/21/mt-leaf.html" target="_blank" rel="external">Leaf——美团点评分布式ID生成系统</a><br>面试中常见的问题：如何生成唯一ID？没有标准答案，关键是思考的过程。<br>类似的还有如何设计一个短链系统。</p>
<p><a href="https://blog.csdn.net/b6ecl1k7bs8o/article/details/85085794" target="_blank" rel="external">SQL 查询优化原理与 Volcano Optimizer 介绍</a><br>前段时间听了tidb的一些分享，介绍了sql执行时的一些优化，向量化/codegen之类的。<br>不过我对这块不太了解，火山模型也只是知道而已，毕竟不是专业搞数据库的，感兴趣的可以参考<a href="https://15721.courses.cs.cmu.edu/spring2019/schedule.html" target="_blank" rel="external">CMU 15-721</a>。</p>
<p><a href="https://www.infoq.cn/article/an-article-mastering-sql-on-hadoop-core-technology" target="_blank" rel="external">一篇文章掌握 Sql-On-Hadoop 核心技术</a>：比较全，也比较简略</p>
<p><a href="https://www.infoq.cn/article/MLMyoWNxqs*MzQX7lvzO" target="_blank" rel="external">Apache Kafka 从 0.7 到 1.0：那些年我们踩过的坑</a><br>介绍了kafka的发展历史，正如文中所说：“它的设计理念非常简单，就是一个以 append-only 日志作为核心的数据存储结构”</p>
<p><a href="https://www.infoq.cn/article/kafka-vs-rabbitmq" target="_blank" rel="external">消息中间件选型分析：从 Kafka 与 RabbitMQ 的对比看全局</a><br>看点不在于kafka，而在于各种MQ特性的总结：优先级队列/延迟队列/重试机制/消费方式/push or pull/持久化/消息过滤/多租户/协议支持/顺序性/事务等等。</p>
<p><a href="https://www.infoq.cn/article/2018%2F08%2Frocketmq-4.3-release" target="_blank" rel="external">RocketMQ 4.3 正式发布，支持分布式事务</a><br>名义上是讲rocketmq，实际上讲了分布式事务的各种解决方案。SAGA这个东西，第一次听说。<br>消息事务的本质，还是两阶段提交。</p>
<p><a href="https://stackoverflow.com/questions/34408970/kafka-consumer-offset-max-value" target="_blank" rel="external">Kafka consumer offset max value?</a>：一个好玩的问题，如果offset超过long最大值了咋整</p>
<p><a href="https://mp.weixin.qq.com/s/ZH5e-ZW1mLnAEelL76wxuA" target="_blank" rel="external">蚂蚁数据分析平台的演进及数据分析方法的应用</a><br>我入职了蚂蚁也没看到这么个平台啊。。。不过大公司内部各种轮子造的真是挺多的。<br>文章讲的还是非常全面，不光是数据分析领域，对数据平台各个组件的介绍也非常有借鉴意义，对数据分析平台的痛点和演进过程分析的也很好，虽然有点抽象。<br>3.0版本虽然看着好像很nb，但从介绍的一些技术来看，似乎还是为了加速查询？</p>
<p><a href="https://www.infoq.cn/article/yeQU4f_BujTYCMxaXNFc" target="_blank" rel="external">BAT 程序员们常用的开发工具</a><br>似乎最开始有个“阿里程序员常用开发工具”，现在演变出“BAT程序员常用开发工具”了。。。文章内容还是比较实用的。</p>
<h1 id="碎碎念">碎碎念</h1>
<ul>
<li><p>我一直在想数据的价值要如何体现，但不得不悲观的承认从数据中淘金是很难的。如果不是被逼的，没人愿意做这种苦力活。如果还是以前那个流量红利的时代，谁还关心什么数据啊，野蛮成长就好了。<br>虽然大家都承认数据很重要，但数据本身很难作为核心业务（又不能直接卖对吧），更多还是提供一些附加价值。目前看来最主要的价值体现在运营上，比如用户运营、流量运营。而且大多数使用方式还比较初级，属于“验证想法”：我有一个假设，用数据来验证是否正确。而大家一直在探索的，是如何让数据“产生想法”，目前还没有很好的解。</p>
</li>
<li><p>在大数据和AI的发展过程中，总会出现所谓的“万能论”。什么东西搞不定了，让算法同学解决下；什么现象解释不了，让数据同学看一下。而且一个很危险的倾向是为了用而用，什么都想往算法上面靠，反而把事情搞得很复杂。正如前几年比特币很火的时候，养个鸡都要用上区块链。很多时候，基于规则的解决方案往往比一个不可解释的算法更好，现实中的大多数系统也是规则+算法的结合。即使是在算法领域，LR这种容易解释的模型也比ANN这种难以解释的应用范围大得多。</p>
</li>
</ul>
]]></content>
    <summary type="html">
    <![CDATA[<center>“若无闲事挂心头，便是人间好时节。”</center>

]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[江湖很小，总会再见]]></title>
    <link href="http://jxy.me/2019/05/29/see-you/"/>
    <id>http://jxy.me/2019/05/29/see-you/</id>
    <published>2019-05-29T02:38:37.000Z</published>
    <updated>2019-05-31T08:22:22.000Z</updated>
    <content type="html"><![CDATA[<p>还是得写点什么。</p>
<a id="more"></a>
<p>最近又要换工作了，虽然不是第一次，但每次多多少少都会有些感慨。</p>
<p>对于<a href="https://www.u51.com/" target="_blank" rel="external">51</a>这家公司，我还是心怀感激的，在这里我成长了很多，思考了很多，也结识了很多人。虽然离开，但没有太多遗憾。在这里我推动了一些事情的从无到有，为公司数据体系的完善尽了绵薄之力，建设各种基础设施，也收获了内外部的很多认可。我自身也有了更加全局的视野和规划，有了更多技术之外的思考，变得会撕逼、会争吵、会带着同学们一起冲锋陷阵。当然也经历过各种奇葩事情，算是锻炼了心理承受能力。所有这一切，都会是人生中的一笔宝贵财富。<br>虽然离开，但期待还会再见，还能与同学们重聚。</p>
<h1 id="鸡汤时间">鸡汤时间</h1>
<p>职业规划这个东西，我一直在跟同学们讲，但说实话我自己也是在不断摸索，只不过是因为痴长几岁，有些经验和踩过的坑能分享罢了。这种问题没有人能告诉你答案，每个人的路径都是不可复制的，只能参考。毕竟每个人所求的都不一样，求仁得仁已是侥幸，何况其他。</p>
<p>对我而言，工作最关注的无非两点：</p>
<ol>
<li>成长空间。未必是技术的成长，而且也要判断是否是自己期望的方向，自己付出的努力的“性价比”。最怕的是，一眼能看到上限。就像<a href="https://zh.wikipedia.org/zh/JoJo%E7%9A%84%E5%A5%87%E5%A6%99%E5%86%92%E9%99%A9" target="_blank" rel="external">JOJO</a>（暴露了JO厨本质）里的角色一样，其他属性再差，只要成长性高，就值得期待；</li>
<li>跟优秀的人在一起。这是一个很容易被忽略的因素，近朱者赤不是随便说说。最怕的就是变成所有人一起“比烂”；</li>
</ol>
<p>但很多时候，我们能做的事情不多。在各种反鸡汤里，“努力”是最不值钱的了，就像五光十色的肥皂泡，是资本家描绘了一个个“靠努力改变命运”的童话，诱使你听话、老老实实受压榨，变成所谓的“奋斗逼”。在他们看来，真正重要的是“天赋”，是“背景”，是“机遇”和“选择”。我看到很多人随波逐流，似乎活的也不错嘛。如果能碰巧跟上大潮流（比如买房、比如上市），还能实现“阶级跨越”。</p>
<p>但这其实是很多人用来逃避的借口，就像著名的“读书无用论”、“大学生给初中生打工”一样。天赋/背景，是你能改变的么？还是大家都认命好了，混吃等死，等着所谓的“机遇”找上门来？即使真的找上门来，你有能力抓住么？</p>
<p>有些事情是改变不了的，正如<a href="https://www.douban.com/group/topic/26840085/" target="_blank" rel="external">尼布尔的祷文</a>所说：“请赐予我勇气去改变我能改变的，请赐予我平静去接受我不能改变的”。</p>
<p><em>保持身体健康，努力从各方面提升自己，保持一颗“搞事”的心；时刻做好准备，在碰到机会的时候，狠狠抓住，但即使错过也不要怨天尤人，受困于过去。</em></p>
<p>足矣。</p>
<h1 id="关于搞事">关于搞事</h1>
<p>我最近在想，我去到一家公司，能带来什么样的“变化”？当个螺丝钉？那也太无趣了，而且这螺丝钉也太贵了一点。。。</p>
<p>以51为例，大言不惭的说，如果没有我，数据平台就不是今天这个样子，无论是技术上（基础设施、系统、架构）还是非技术上（团队、人员、氛围）。当然我也要吐槽自己，很多地方做的也不好。但这种“变化”，才是我带来的“价值”。要不招我进去和招一个应届的有啥区别？这样一种“寻找变化”、“搞事”并且实现自身价值的心态，说起来很容易，但很多人都缺少。毕竟么，很多人都是求稳的，舒适区也是真的舒服。<br>还有一些人是受限于自己的位置，觉得自己人微言轻，“不关我事”。这个怎么说呢，如果是一个相对开放的环境，都会很鼓励去做尝试的。我也一直和同学们说，别怕去尝试各种东西，有事故我担着，就怕你没想法。</p>
<p>当然不同情况下，“搞事”的难度是不同的，但关键是态度。<br>当不能带来变化时，也许就是离开的时候了。</p>
<h1 id="END？">END？</h1>
<p>我最近也在反思，是不是我活的太认真了，总是想梳理出事情内部的逻辑，让理性决定自己的下一步行动。。。太纠结，有点累。其实我很不喜欢自己这一点。有朋友评价我是“自虐型人格”，貌似还有点道理。。。而且这让我鄙视各种捷径/小聪明/投机，有些书生气/理想主义，只顾自己的“正道”，不能快速做出反应，也错过了很多机会。以正立身，以奇制胜，相辅才能相成。</p>
<p>anyway，向前看吧，“未来”的魅力就在于它的无限可能性。<br>以后还是多写点技术的吧。。。鸡汤虽好，不能贪杯。<br>最近算是完全放松下来了，能有时间把以前堆积的各种书看完，也是一桩幸事。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>还是得写点什么。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[《Streaming System》]]></title>
    <link href="http://jxy.me/2019/03/07/streaming-system/"/>
    <id>http://jxy.me/2019/03/07/streaming-system/</id>
    <published>2019-03-07T11:29:53.000Z</published>
    <updated>2019-03-16T10:59:06.000Z</updated>
    <content type="html"><![CDATA[<p><a href="/2018/11/25/meta-gossip/">之前</a>就提到过，最近在看<a href="http://streamingsystems.net/" target="_blank" rel="external">《Streaming Systems》</a>这本书。趁着过年期间再加几个周末，终于看完了。自从<a href="/2015/10/12/hadoop-application-architectures/">《Hadoop Application Architectures》</a>之后，很少这样细致的看一本英文大部头了。</p>
<p>我对流处理并不很熟悉，随便写写感想，可能有错误，也并不全是流处理相关的。</p>
<a id="more"></a>
<h1 id="关于本书">关于本书</h1>
<p>本书并不是针对某个系统去讲解原理或使用，也并不是针对的讲某种技术，而是从比较抽象的角度去阐述“流处理系统应该是什么样子的”、“应该具备哪些能力”，可以作为系统设计时的参考。如果一定要类比的话，有点类似于《数据库系统概念》这种书。<br>所以本书并不适合初学者读，如果没有一些数据处理的实际经验（无论stream还是batch），上来就直接看各种概念，很容易懵逼，不知道为什么要这样设计。但反过来说，如果你有一些经验再来看这本书，会有种融会贯通的感觉。就好像你看到的不只是流处理系统，而是整个数据处理领域/相关系统的发展脉络和设计哲学，虽然这么说比较玄学。。。</p>
<p>看书时的几点提示：</p>
<ul>
<li><a href="https://learning.oreilly.com/library/view/streaming-systems/9781491983867/" target="_blank" rel="external">Safari Books Online</a>真是非常好用，试用期内可以免费在线看书，不过还是推荐充值。</li>
<li>Safari Books Online的另一个好处在于，本书的很多示意图是动态的，如果你只看纸质版本，很容易看不懂。而且很多概念本身有点抽象，如果你刚碰到时看不懂，很可能后面的章节也就完全不懂。所以一定要看懂动图、一定要看懂动图。</li>
<li>可以和google的<a href="https://ai.google/research/pubs/pub43864" target="_blank" rel="external">Dataflow论文</a>结合着看。Dataflow可以指的是google的数据处理<a href="https://cloud.google.com/dataflow/" target="_blank" rel="external">系统</a>，也可以指的是它提出的那套模型和API，下文中我会混用。</li>
<li>作者的用词有一点奇怪，经常会出现一些很生僻的单词。。。而且有些英文的梗实在是不太懂，大概因为我不是native english speaker吧。</li>
</ul>
<p>话说，阿里的哥们不是说在<a href="https://www.infoq.cn/article/the-evolution-of-large-scale-data-processing" target="_blank" rel="external">翻译中文版</a>么，说是年末上市，这都9102年3月了，也没看到。。。</p>
<h1 id="啥是framework">啥是framework</h1>
<p>看书的过程中，我一直在想，各种流处理系统之间的差别到底是啥，为什么以前storm一片繁荣，而现在面对flink败下阵来。如果直接对比功能特性，我们当然能列举出很多：window啊、exactly-once啊、event-time啊。但如果一定要较真，其他框架不能实现这些么？应该可以，但是有些side effect，而且需要用户自己去操心很多事情。如果用storm去实现event-time窗口，需要很小心的设计，很容易出错，而这实际上跟业务逻辑无关，实际的业务代码仅仅是系统代码很小一部分，不值得让用户付出心力。<br>前段时间看了介绍Jeff Dean的一些文章，讲了MapReduce的一些历史。无非就是以前大家在处理数据时都要从头开始写很多代码，各种烟囱式开发，而MR将这些抽象出来，让用户更能关注自己的逻辑。虽然以现在的眼光来看，MR也是很low-level的API了。<br>为什么SQL大行其道，很重要的原因在于，用户只是在“表意”，只关心自己的逻辑就可以了。</p>
<p>从这个角度来讲，framework或者说框架，其意义在于屏蔽底层（尤其是infra）的各种细节，把各种脏活累活统一做掉。最典型的脏活就是failover，让用户自己去操心节点失败后如何恢复，闹呢？<br>框架做的越多，应用层需要做的就越少，这个边界其实挺难划的，而且不同时代需要的框架能力也不同。flink由于支持dataflow模型，这条线划的比较高，或者说抽象程度更高一些，恰逢其时。</p>
<p>不只是数据处理领域，各种JavaEE框架、各种ORM、各种算法框架，甚至于各种library/utils、微服务、RPC、操作系统、编程语言，都是同样的演进过程。所以计算机科学，是不是就是一门关于“抽象”的科学？这在其他学科中好像还是比较少见的。<br>正如本书作者所说：Hooray for layers of abstraction!</p>
<p>甚至在非技术领域也是如此，比如著名的<a href="https://wiki.mbalib.com/wiki/KISS%E5%8E%9F%E5%88%99" target="_blank" rel="external">KISS原则</a>。我们在设计自己的产品时，是否真的帮用户把所有他不需要关心的事屏蔽掉了？说来惭愧，总是有人吐槽我们的系统不好用/反人类，我觉得就是暴露给用户的、需要用户操心的东西太多了。</p>
<p>不过话又说回来，过度的封装也可能带来问题。比如spark/flink中user code可能被并行的执行，如果API层面不显式的让用户感知到这一点，用户的理解偏差可能带来各种奇怪的bug。很久之前看过的一篇文章《<a href="https://blog.codingnow.com/2015/11/rpc.html" target="_blank" rel="external">RPC之恶</a>》也表达过类似的观点：过度的抽象看似简化，其实会增加整体的复杂性。</p>
<p>这些道理虽然很简单很朴素，但却很容易被忽略。</p>
<h1 id="流_vs_批">流 vs 批</h1>
<p>一个很本真的问题：流处理和批处理到底差别在哪里？</p>
<p>因为我们都是搞工程的，所以我们很容易列举出一些区别：</p>
<ul>
<li>批处理的数据是已知的、不可变的，流处理的数据往往是未知的、实时输入的</li>
<li>流处理结果更实时，但相对并不准确，所以才会有所谓的lambda-architecture</li>
<li>流处理需要有daemon进程常驻</li>
<li>批处理往往是无状态的，而流往往是有状态的</li>
<li>因为没有状态，批处理的failover只要重跑就可以了，而流的错误处理会更麻烦</li>
<li>批处理由于能获取一些“全局”状态，可以做一些针对性优化，吞吐量更高</li>
<li>批处理的join实现已经比较完善了，而流的join一直是个大难题</li>
<li>等等等等</li>
</ul>
<p>但所有这些区别，更像是“结果”，是工程实现的差别，而不是本源。大家之所以觉得流处理系统不准确、吞吐量不高等等，只是因为它被实现成了那个样子。<br>本书最重要的一个理念，就是从更高的抽象层面，论证了批和流实际上是能统一的（作者实际上说的是stream是batch的超集，但我有点存疑，或者作者说的stream其实是更广义上的？）。我们不应该从工程实现的角度来分类，而应该从处理的问题域来划分。</p>
<p>在真实的场景中，大多数数据其实都是无界（unbounded）+ 无序（unordered）的。很多我们用批处理系统（比如MR）去处理的有界数据，其实是一种“劣化”。亲身经历的场景：</p>
<ul>
<li>late data问题。以前用MR去计算各种报表，由于移动端的特殊性，上传必然是有延迟的。计算T+1报表也必然会少一部分数据，于是需要每天重跑过去3天的数据。为什么是3天呢，拍脑袋定的。。。日志延迟半个月都有可能，但重跑半个月之前的报表已经没啥意义了。</li>
<li>session计算问题。以前通过MR去计算T+1的session，但用户的session很可能是跨天的，甚至可能跨几天。一般会多取几个小时的日志，但我也不知道应该多取多久的。只能“一刀切”：凌晨2点之后的日志全部不算在内。</li>
</ul>
<p>从这个角度来讲，面对这种unbounded数据，批处理只能划分成一个个小的batch来处理，结果其实是不准确的。而流处理天生就考虑到了数据的无界性，理论上准确性应该是优于批的。</p>
<p>那为啥大家还会留下批处理结果准确、流处理结果不准确的印象嘞？这其实是两个层面的概念：accuracy和completeness。批处理的“准确”，指的是计算的accuracy，更多是系统层面：恒定的数据+恒定的计算逻辑=恒定的结果，就像不可变函数一样。即使这个最终结果不准确，也是计算逻辑或者数据的锅，而且误差也是相对恒定可接受的；流处理的“准确”，指的是数据的completeness，理论上能计算更准确的结果，但以前的各种流处理系统由于不能保证（或是实现代价很高）exactly-once，所以实际上算出来的可能不准。即使是同样的数据+同样的处理逻辑，你跑多次结果都可能是不一样的。。。但这不是流处理语义上的锅。</p>
<p>即使是有界（bounded）数据，按无界数据的方式来处理，准确性（accuracy）上也没有什么问题。而且很多我们所谓的有界数据，其实都不是真正的有界。</p>
<p>所以stream和batch更像是处理同一个问题的两种方式，真正的区别在于latency和throughput，这是一种trade-off（有句名言怎么说的，“distributed systems are all about trade-offs”）：</p>
<ul>
<li>latency：用stream的方式去处理时，你可以提前观察到结果，虽然这个结果可能不准，是一个提前观测甚至预测。但流处理语义保证当数据完整时结果是正确的（所谓的refinements of results）。而用batch的方式去处理时，如果想要正确的结果，必须保证数据完整后才能开始计算。</li>
<li>throughput：batch方式吞吐量更高，因为有全局的数据，可以对shuffle做更多优化。而且failover简单，也就不需要持久化很多状态。而stream的方式需要提前计算结果，消耗更多资源，而且failover需要保存更多中间状态，所以吞吐相对没有batch高。<ul>
<li>题外话，关于shuffle：毫不夸张的说，所有的分布式计算框架，最重要的就是shuffle过程。shuffle的语义是否强大决定了框架的能力，shuffle的实现方式决定了框架的效率。</li>
</ul>
</li>
</ul>
<p>问题域统一后，流和批的API也是可以统一的（Dataflow/Beam）。这也很好理解，如果要解决的问题是相同的，而API是更多表意的（不掺杂太多底层逻辑），那表达方式应该也是相同的。</p>
<p>真正麻烦的在于引擎层面（或者说runner），目前还没有一个很好的解，估计很长一段时间内也都很难统一。现阶段Beam这套API，还是用于流处理更实际一点。即使flink宣称同时支持流和批，实际上批处理那套API也没啥人用。</p>
<p>所以上面说的各种统一更多是理论上的，我们作为工程开发人员还是要实际一点。。。仰望星空，脚踏实地嘛。不过这应该是未来可以探究的一个方向，各大公司也有很多投入（<a href="https://cloud.google.com/dataflow/" target="_blank" rel="external">Google Cloud Dataflow</a>/<a href="https://databricks.com/product/databricks-delta" target="_blank" rel="external">Databricks Delta: Unified Analytics Engine</a>），嘴上说着没有one size fits all/no silver bullet，身体还是很诚实的。。。</p>
<h1 id="Stream_vs_Table">Stream vs Table</h1>
<p>本书认为衡量数据的特性应该从两个维度出发：cardinality和constitution（看到这两个词直接懵逼了，不查根本不知道什么意思）。</p>
<ul>
<li>cardinality：数据的规模，是有界还是无界；</li>
<li>constitution：数据的组织方式，或者说对外的表现形式，有两种：stream和table；</li>
</ul>
<p>就像波粒二象性一样，stream和table其实是针对数据的两种视角（不过应该是特指结构化数据，书中没有明确写出来）。这么说有点抽象，最直接的例子就是mysql的binlog：binlog代表了数据变化的过程，应用binlog后的table代表着数据的一份快照。另一个例子就是物化视图，视图会随着数据源的变化而更新（可以认为是接收到了一条数据变化的消息通知）。<br>或者这么说，在大多数database中，虽然呈现给我们的是table形式，但实际上的底层都存在一种append-only的数据结构（innodb的redo log，hbase的WAL）。呈现出来的table形式，是更上一层的“抽象”。这种机制也被称作CDC（<a href="https://en.wikipedia.org/wiki/Change_data_capture" target="_blank" rel="external">Change Data Capture</a>），不光在数据库领域，在其他领域也是非常常见的。</p>
<p>于是作者提出了stream和table的相关性（streams and tables are really just two different sides of the same coin）：</p>
<ul>
<li>Streams → tables：The aggregation of a stream of updates over time yields a table.</li>
<li>Tables → streams：The observation of changes to a table over time yields a stream.</li>
</ul>
<p>另一种表述：</p>
<ul>
<li>Tables are data at rest.</li>
<li>Streams are data in motion.</li>
</ul>
<p>其实点破的话，也没那么难理解，虽然有点抽象。但是然后嘞？就算论证了stream和table的相关性，意义在哪里？这就有点纯理论研究的性质了：</p>
<ul>
<li>任何data processing system，无论流还是批，其实就3个要素：stream、table、operation<ul>
<li>作者论证了现有的各种系统（MR、Beam等）都能适配到这个理论中，具体的论证过程不详述</li>
<li>但是各种系统中会有偏向（biased），比如Beam中，stream才是first class citizen，table的概念是隐式的，MR则反之</li>
</ul>
</li>
<li>operation会导致stream和table之间的变换，共有4种情况：<ul>
<li>stream → stream：nongrouping operation，或者说是对单个元素的处理（filter/map等），很好理解，storm等传统意义上的流处理都是针对的这种情况</li>
<li>stream → table：grouping operation，或者说是需要shuffle的操作，shuffle后每个operator需要保存一个internal state，其实就是这个所谓的table，典型的就是各种聚合（参考spark中的stage划分策略）</li>
<li>table → stream：ungrouping operation，典型的就是dataflow中的各种trigger</li>
<li>table → table：不存在，所有对table的修改都是通过先ungroup再group实现的</li>
</ul>
</li>
<li>更关键的一点：对于table我们已经有了很完善的处理手段（比如SQL），而这些手段都是能直接应用于stream的，至少理论上是完备的</li>
</ul>
<p>关于StreamSQL，本书中有专门的一章来讨论，作者描述了一种比较完善的流式SQL语义。其大概思想就是在传统的关系代数（RDBMS的理论基础）中加上时间这一维度，用所谓的TVR（Time-Varying Relations）来描述各种变换，进而扩展现有的SQL语法（前提是尽量精简，保证一个最小集）以支持流式处理。感觉上，有一点像数仓理论中的拉链表？表中的每条数据，其实都是有生命周期的。<br>不过作者也承认，这是一个比较理想的模型，现有的各种系统没有一个真正实现了这套完备的SQL语义。好像ANSI也在讨论流式SQL的标准，准备制定规范了？不知道最终的规范会是什么样。</p>
<p>总的来说，这个“stream/table二象性”还是比较抽象的，更多是一种理论上的指导意义。了解下可以，别过于纠结。</p>
<p>P.S.：关于append-only和CDC，还有一篇经典文章：<a href="https://github.com/oldratlee/translations/tree/master/log-what-every-software-engineer-should-know-about-real-time-datas-unifying" target="_blank" rel="external">The Log</a>。</p>
<h1 id="状态是万恶之源">状态是万恶之源</h1>
<p>很早以前我也这么说过，不过那时针对的是<a href="/2016/09/06/zhen-second-hand-qianduan/">React</a>。</p>
<p>不只是在分布式领域中，哪怕是单机的业务系统，我们也要很小心的设计状态，包括各种状态的转移、状态的持久化、状态的恢复等。在面对复杂的业务规则时，使用状态机也往往是一个比较好的选择。完全没有状态似乎不太可能，不过合理使用shared storage可以尽量减少维护成本。</p>
<p>说回流处理领域，如果我没记错，storm里是没有状态的概念的，所有的一切都要自己管理，第一个完善状态管理的好像是samza？状态带来的问题主要有两方面：</p>
<ul>
<li>failover：一个有状态的task/node，挂掉后如何恢复？</li>
<li>rescale：一个有状态的operator，修改parallelism后，状态如何拆分？（operator/instance的概念需要区分）</li>
</ul>
<p>实际中讨论的更多的问题好像还是failover，rescale的问题我只在flink的文档中看到一些，动态修改并行度一直都是个大难题。</p>
<p>如果说的抽象一些，所谓的状态就是上文中“stream/table二象性”理论中“stream → table”变换生成的table。这个“table”往往都是各个operator自己维护在内存中，不对外暴露，待到trigger触发后才对外emit一个值。不过本书中提到，如果这个internal state可以直接对外暴露，很多情况下可以省略掉sink阶段，flink中也有一些类似的做法：<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/dev/stream/state/queryable_state.html" target="_blank" rel="external">Queryable State</a>。</p>
<p>如果从状态的分类来说，大概可以分为两种：</p>
<ul>
<li>operator内部的状态，比如window的聚合、kafka的offset等；</li>
<li>transformation的中间状态，用户也可以将自己code中的状态交给系统托管；</li>
</ul>
<p>感觉上，系统提供了一个托管状态的“容器”，所有在这个容器中的状态，其生命周期、持久化、failover都由系统管理。如果你写自己的transform逻辑时，非要自己维护状态（比如在redis中），也不是不行，谁也拦不住对吧。（“我就是要出狂战斧”，这个梗还有多少人懂。。。）<br>由系统维护的状态，如果要保证其可靠性，要么写到shared storage中，将可靠性交给底层存储来保证；要么就是定期checkpoint，相当于定期备份。其实很多系统的机制都差不多，比如hdfs的editlog+fsimage。至于备份的状态失败时如何恢复、如何split，那就是另外一个话题了，跟下文要讨论的exactly-once也有关。</p>
<p>参考flink相关文档：<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/dev/stream/state/state.html" target="_blank" rel="external">Working with State</a>和<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/internals/stream_checkpointing.html#checkpointing" target="_blank" rel="external">Checkpointing</a>。</p>
<h1 id="exactly-once">exactly-once</h1>
<p>老生常谈了，为啥流处理经常被人吐槽结果不准，还有人说“storm是弱一致性的”（“一致性”这个词在个场景是很模糊的，我觉得就等价于exactly-once），就是因为很难保证exactly-once语义，数据处理过程很难做到replayable。同样的输入每次计算的结果都可能不一样（nondeterministic），让别人如何相信你（虽然这种不确定性不完全是由于框架本身导致的）。。。所以才有了Nathan Marz的lambda-architecture，但也带来了各种其他问题，更像是一个过渡方案。</p>
<p>为啥exactly-once这么难，根本原因还是在于failover，或者说fault tolerance（在分布式的环境下框架不可能忽略这个问题）。如果框架要处理节点失败的情况，至少也是at-least-once。我们要考虑的是如何在此基础之上达成exactly-once。<br>需要注意的是，exactly-once并不保证user code只运行一次，所以如果在代码内部做了一些外部操作，比如访问外部系统服务，还是可能有副作用的。数据也未必真的只是处理一次，只要保证最后的结果与“只处理一次”的场景下一致就可以了。</p>
<p>如果细分的话，exactly-once其实分3个层面，只有这三个层面都得到保证，才是完整的端到端exactly-once语义：</p>
<ul>
<li>shuffle：every record is shuffled exactly once，或者说是系统内部的exactly-once语义。非shuffle的场景下（比如单个stage内）通常都能隐式的保证exactly-once。</li>
<li>source：every source record is processed exactly</li>
<li>sink：every sink produces accurate output</li>
</ul>
<p>很多系统（包括flink的旧版本）宣称提供exactly-once，但实际上只能保证第一个层面，即shuffle。</p>
<p>shuffle的exactly-once的常用解决方法通俗点说就是去重。at-least-once是如何保证的？一般是上游直接重试，比如storm的ack机制，这种机制也被称作upstream backup，由上游保证消息被正确传递，ack丢失时就可能出现重复。如果在接收端维护一个hashmap之类的结构，给每条消息分配唯一的ID，理论上就可以避免重复。当然实际上肯定没这么简单，书中还提到了graph optimization、bloom filter等策略以提高效率。<br>书中还讨论了另一个有趣的case：部分transformation的结果是nondeterministic的，尤其是用户自己写的代码。比如每次去查一下redis再决定输出什么值。如果发生了失败和retry，很可能每次重跑的结果都是不一样的，虽然理论上讲这个锅和系统本身无关。为了应对这种情况Dataflow会在shuffle前将每个stage的输出的值加上unique id写到stable storage，再发送到下一个stage。发生retry时，直接使用stable storage中的值输出给下游。这种场景大多数框架都不会考虑吧，也许google内部有什么特殊需求，实现中也必然还是有一些优化的。</p>
<p>除了这种常见的重试（at-least-once）+去重的策略，还有个画风不太一样的就是flink。它通过自身的checkpoint机制（其实就是当前pipeline的状态的快照），当出现错误时直接回滚到上一个checkpoint，而且是全局回滚。理论上来讲这种操作代价还是比较大的？仅适用于错误概率比较小的情况吧。从这个角度来说，flink不是failover，而更像是failback。<br>而且通过对checkpoint机制的扩展，flink衍生出了<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/ops/state/savepoints.html" target="_blank" rel="external">savepoint</a>的概念，可以从任意savepoint重启整个pipeline，还是挺nb的。看了下逻辑上似乎就是一个 &lt; operator-id, state &gt; 的map，只要从这个savepoint重启，就会按照id去恢复状态。甚至拓扑结构完全不一样都可以，只要id能对上。</p>
<p>source/sink端的exactly-once保证更多要依赖于具体的存储系统，比如kafka。<br>source端为了防止重复消费，常见的策略也是去重。在kafka 0.11之前，都是需要每个source端自己去做去重（应该是根据partition+offset吧）；0.11版本之后引入了幂等和事务特性，理论上不再需要source端处理。<br>sink端似乎是最麻烦的，借用<a href="http://spark.apache.org/docs/latest/streaming-programming-guide.html#semantics-of-output-operations" target="_blank" rel="external">spark的文档</a>中的说法，有两种方式：</p>
<ul>
<li>Idempotent updates：sink还是可能重跑，输出多个重复值，但存储系统是幂等方式存储的，比如文件、KV这种每次覆盖；</li>
<li>Transactional updates：这个比较好理解，如果retry，上一次输出的值其实没有commit；</li>
</ul>
<p>理论上来讲，去重策略在这里还是可以用的吧？sink在输出前先查下外部系统，判断是否已经输出，就是代价可能比较大。</p>
<p>flink是将kafka的事务特性与checkpoint机制结合（<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/dev/connectors/kafka.html#kafka-producers-and-fault-tolerance" target="_blank" rel="external">Kafka Producers and Fault Tolerance</a>），引入两阶段提交才实现的exactly-once，好像还挺复杂的我也没仔细看。</p>
<p>总的来说，感觉exactly-once已经不是那么遥不可及的的一个东西了，之前很多人甚至说exactly-once是不可能的。当然可能他们定义的exactly-once更严格，也许是学术界与工业界的gap？</p>
<p>参考资料：</p>
<p><a href="https://bravenewgeek.com/you-cannot-have-exactly-once-delivery/" target="_blank" rel="external">You Cannot Have Exactly-Once Delivery</a><br>文章比较老了，还是可以看看</p>
<p><a href="https://streaml.io/blog/exactly-once" target="_blank" rel="external">Exactly once is NOT exactly the same</a><br>直接对比了Distributed snapshot和at-least-once event delivery plus deduplication</p>
<p><a href="https://my.oschina.net/u/992559/blog/1819948" target="_blank" rel="external">Apache Flink 端到端（end-to-end）Exactly-Once特性概览</a></p>
<h1 id="其他">其他</h1>
<p>以上我只总结了自己印象深刻的几个点，也不全是书里的内容，加上了自己的一些理解。</p>
<p>书中还有很多其他有意思的东西，不一一赘述了，比如：</p>
<ul>
<li>watermark概念也是很有意思的，还有low/high watermark的设计理念上的差别</li>
<li>session的计算、window的合并</li>
<li>不同的Accumulation模式：discarding、accumulating、retracting</li>
<li>在event-time时间域实现process-time处理逻辑</li>
<li>StreamJoin的各种实现策略，老大难问题<ul>
<li>inner join还好，就是在join operator中要保存很多状态</li>
<li>outer join还会带来额外的retraction成本</li>
</ul>
</li>
<li>所谓的What/Where/When/How等等</li>
</ul>
<p>作者在QCon等场合也有一些presentation，可以结合着一起看。</p>
<p>其实很多问题都不难想到，只要你真实使用MR、storm之类做过数据处理。比如时间漂移/状态存储/重复消费等等，dataflow只不过把这些问题抽象化、系统化了，并且在一个框架内提供了统一的解决手段。如果之前没有经验，可能很难理解为啥google要搞得这么复杂，这么多概念。</p>
<p>最后说说flink，这应该是最近最火热的流处理框架了吧。总结原因大概有几点：</p>
<ol>
<li>语义上更完备，支持dataflow模型，对于无界、无序数据提供更好的支持；</li>
<li>全局快照机制，解决了状态问题并基于此提供强一致性保证。而且还玩出了花样，又是增量又是异步的，并衍生出了savepoint能力； </li>
<li>SQL能力的加持；</li>
<li>开源社区活跃 + 大公司站台；</li>
</ol>
<p>以后有时间还是要好好研究下。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p><a href="/2018/11/25/meta-gossip/">之前</a>就提到过，最近在看<a href="http://streamingsystems.net/" target="_blank" rel="external">《Streaming Systems》</a>这本书。趁着过年期间再加几个周末，终于看完了。自从<a href="/2015/10/12/hadoop-application-architectures/">《Hadoop Application Architectures》</a>之后，很少这样细致的看一本英文大部头了。</p>
<p>我对流处理并不很熟悉，随便写写感想，可能有错误，也并不全是流处理相关的。</p>
]]>
    
    </summary>
    
      <category term="bigdata" scheme="http://jxy.me/tags/bigdata/"/>
    
      <category term="streaming" scheme="http://jxy.me/tags/streaming/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[元吐槽]]></title>
    <link href="http://jxy.me/2018/11/25/meta-gossip/"/>
    <id>http://jxy.me/2018/11/25/meta-gossip/</id>
    <published>2018-11-24T18:55:38.000Z</published>
    <updated>2018-12-23T08:42:03.000Z</updated>
    <content type="html"><![CDATA[<p>又要开始胡言乱语了。</p>
<a id="more"></a>
<p>其实这个md文件的创建时间是“2018-05-28 02:55:38”，却拖了半年才写完。。。时间跨度比较大，各种内容比较杂，想到哪写到哪。我的习惯就是想到什么先记下来，然后慢慢去补充。</p>
<p>为啥叫“元吐槽”呢，大概是“关于吐槽的吐槽”吧。</p>
<h1 id="元数据">元数据</h1>
<p>元数据系统应该是数据平台中最“玄学”的系统了，很多人认识不到它的价值，也很难说清楚它到底是做什么用的。如果你去搜索“元数据”的定义，一般都会告诉你元数据是“关于数据的数据”，这特么就是废话。。。你还是不知道它是啥。就像《易经》一样，你可以说它包罗万象，也可以说他废话连篇。因为它太抽象，每个人总能找到自己的方式去“适配”它，都会有自己的理解，还都觉得自己是对的。还有人说：哲学是关于科学的科学、哲学是科学之母，一个道理。各种方法论/各种主义，一向是我吐槽的重灾区。。。</p>
<p>扯远了。元数据不是个新鲜概念，而且非常宽泛，任何系统运行时需要的数据，都可以叫做元数据。所以别纠结，我们讨论的更多是大数据场景中的元数据和对应的系统。</p>
<p>就像大数据领域很多系统都以Google为原型一样，关于元数据Google也有一篇论文：<a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/45390.pdf" target="_blank" rel="external">Goods: Organizing Google’s Datasets</a>，介绍了google如何管理自己的数据集（Dataset），包括自动发现、组织、计算血缘、检索等。通俗点说就是数据集不断增多，但没人知道这些数据是什么，所以需要一个方式来管理。没什么太难的概念，但有几个值的注意的地方：</p>
<ul>
<li>goods非常注重事后处理（post-hoc），给出的理由是不要干扰用户的正常使用。这也是现在业界大多数系统通用的做法，但并不绝对。在并不那么遥远的过去，还有一个概念：EDM（企业数据管理，真是拗口）。感觉上就是所有数据都必须先要在一个中心化的系统中注册，通过这种事前的方式去维护元数据。这也未必就完全是错的。最近接触了一些政府的系统，不同部门之间通过文本文件、excel文件、数据库等传递各种数据，强行事后反而麻烦。事后的前提是有统一的数据存储和处理方式。</li>
<li>goods中的很多元数据信息是“推断”而来，并不能保证准确。典型的就是schema，可能从proto文件中获取，可能从底层文件存储获取。这可能是google特殊的环境导致的。我们不一样，我们有hive啊，schema反而不是问题（当然前提是你所有数据在hive里）。</li>
<li>有些元数据必须是手动补全的，这点非常重要，不要想着程序能做所有的事情</li>
<li>goods倡导的所谓data-culture，不明觉历</li>
</ul>
<p>论文中还一再提到DataLake，感觉这又是最近炒起来的一个概念？（还有所谓的数仓2.0）来回来去就是数据集成整合那点事，摘录一段：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">資料湖泊儲存的資料是最原始的形式，未經過任何處理及管理</div><div class="line">資料湖泊不需要使用者在取得資料前事先建立好資料結構 (schema)</div></pre></td></tr></table></figure>

<p>感觉是各种数据无脑灌进去，需要的时候再去解析。</p>
<p>说回元数据。就像之前说的，我们有hive，所有大多数小公司的元数据系统其实就是依托于hive，数据换一种组织方式，再补充一点自己的业务信息，就成了。很多个人开源的元数据项目，也就这么回事。业界的开源系统：<a href="https://atlas.apache.org/" target="_blank" rel="external">Apache Atlas</a>/<a href="https://github.com/Netflix/metacat" target="_blank" rel="external">Netflix Metacat</a>（最近刚开源的），会支持更多的组件，但大多数情况下应该也是hive为主，而且鲜少见到有实际的应用案例。</p>
<p>结合我们的一些实践，尝试总结元数据的一些特点：</p>
<ul>
<li>很少有自顶向下的设计，都是被逼的。。。大家会更关注于那些“看得到的事情”：hadoop、spark、调度器、数据同步等等。元数据天生弱势，易被忽略。所以每个公司的元数据系统一般是跟整个体系紧密结合的，很少能拿来主义，就算有一些开源项目，也很少能开箱即用。</li>
<li>可以回溯历史：这是最重要的特性，比如hive meta中只会有最新的schema，而元数据系统可以看到历史schema和操作人。这跟数仓有点像，元数据也是要抽象、要建模、有层次的。</li>
<li>很多数据是统计性的，可能不准确，而且不要求/做不到实时更新</li>
<li>受限于上一条特性，也许并不适合作为runtime的依赖，看场景</li>
<li>数据来源庞杂，清洗逻辑也非常复杂（相对于业务的ETL流程而言）</li>
<li>未来的趋势是和在线meta的融合</li>
</ul>
<p>如果是hive表的元数据，大概有哪些：</p>
<ul>
<li>基本属性：owner、schema、分区、变更历史、sample data、对应的调度任务（或者说是表的更新逻辑）</li>
<li>字段属性：安全等级、枚举值</li>
<li>存储属性：大小、文件数、记录数、存储格式、压缩格式、副本数、生命周期</li>
<li>业务属性：业务描述、所属业务线</li>
<li>访问属性：创建时间、更新时间、访问情况（次数+时间跨度）</li>
<li>其他：是否拉链表、是否源头表、增量/全量更新、各种统计信息（比如字段的count distinct）等</li>
</ul>
<p>此外还有各种衍生属性：</p>
<ul>
<li>血缘（表+字段）：最常见的衍生属性</li>
<li>DataProfile：来自阿里的概念，是否废弃表、是否有重复数据、是否不更新、存储健康分等，有点类似用户画像</li>
</ul>
<p>别纠结概念，元数据的概念非常宽泛，上面列的只是比较常见的。</p>
<p>但有这些数据之后嘞？能干啥用？这也是我经常吐槽的点，不要跟我说要计算血缘，而是说要血缘做什么。</p>
<ul>
<li>数据发现：帮助用户找到想要的数据，可能需要引入类似电商的类目和标签体系，还可以借鉴一些概念：收藏夹啊、详情页啊之类</li>
<li>报表：比如计算每个部门/人的账单</li>
<li>存储治理：比如删除无用数据，比如对于冷存的数据降低副本数，比如找到可删除的衍生数据</li>
<li>监控/报警：典型的就是血缘，上游的数据变更要给下游预警</li>
<li>数据服务：这个比较玄学，“正确的组织方式有可能帮助用户发现数据的价值”，就像xmind有助于我们去思考问题一样，关键要将所有的数据视为“资产”去管理，通过数据资产去产生利益（应该没有负资产吧。。）</li>
</ul>
<p>这还只是hive表的元数据管理，更进一步，还有调度任务的、hdfs的、数据源的、kafka topic的、hbase表的等等。<br>广义上来说，运维的CMDB也是元数据系统。甚至可以说一切皆元数据（听着就很神棍）。。。</p>
<p>参考资料：</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzU3OTA5MjY1MQ==&amp;mid=2247483748&amp;idx=1&amp;sn=f696e9fef70a14f4ac9d861a3cb99486&amp;chksm=fd6a28a0ca1da1b6f7675dac05083d775445522fb30e623388e32a5a1e863796bd2e29dd2321&amp;mpshare=1&amp;scene=1&amp;srcid=0415psCeVG2o7mxY5ZuNh1gg##" target="_blank" rel="external">元数据管理系统解析</a><br>打个广告，我老板的文章</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzU1NDA4NjU2MA==&amp;mid=2247489633&amp;idx=3&amp;sn=5279e6d914b21498193b5d6702cc22ca&amp;chksm=fbe9a1aecc9e28b80470fdd134bc74a5102ec24ee777e7d181f83c1e41213b47aab201f53874&amp;scene=27#wechat_redirect" target="_blank" rel="external">自服务数据共享与服务架构详解</a><br>这个文章有点“官方”，讲的有些晦涩，而且更侧重事前处理？<br>痛点分析的不错：过程不可控、数据不开放、缺乏共享。<br>自服务的概念挺有意思，但与post-hoc的理念相悖，而且会带来额外的成本。<br>如何从一个数据的管理者变为数据资产的经营者，带来新的业务？</p>
<p><a href="https://medium.com/@intelisef47/%E4%BD%95%E8%AC%82%E8%B3%87%E6%96%99%E6%B9%96%E6%B3%8A-data-lake-dda35fe6cb6f" target="_blank" rel="external">淺談資料湖泊(Data Lake)</a></p>
<p><a href="https://mp.weixin.qq.com/s/_9WoyQd6ksPKuRbYiFpPdQ" target="_blank" rel="external">重磅！Netflix开源大数据发现服务框架Metacat</a></p>
<p><a href="https://cwiki.apache.org/confluence/display/Hive/StatsDev#StatsDev-Scope" target="_blank" rel="external">Hive wiki: StatsDev</a>  /  <a href="https://issues.apache.org/jira/browse/HIVE-2526" target="_blank" rel="external">HIVE-2526</a><br>hive本身自带一些统计信息，可以作为元数据的一部分，但并不准确<br>而且通过spark sql进行的操作无法统计（有人问hive on spark和spark sql的差别，这就是一例）<br>如果想要准确的数据需要analyze table，但有代价</p>
<h1 id="分布式小杂烩">分布式小杂烩</h1>
<p><a href="/2017/06/03/distributed-mess/">分布式大杂烩</a>续集。<br>最近重看了遍Raft的论文，又引出了一大堆乱七八糟的问题，整理下。</p>
<h2 id="关于CAP">关于CAP</h2>
<p>我一直都知道CAP中的C和ACID的C是不一样的，但是说不清楚。也知道“一致性”这个词非常混乱，业务的一致性和副本的一致性完全是两个东西，甚至还有人说storm是弱一致性的（意思应该是计算结果不准确吧）。直到看了知乎上这个回答：<a href="https://www.zhihu.com/question/56073588/answer/253106572" target="_blank" rel="external">如何理解数据库的内部一致性和外部一致性？</a>，真的是豁然开朗，很多概念都澄清了。像Spanner论文中一直提到的外部一致性，之前就一直似懂非懂。</p>
<p>尝试总结下，其实很多地方可以类比java的并发机制：</p>
<ul>
<li>ACID中的C，核心在于“约束”，事务前后DB的约束不被破坏。什么是约束？其实我们很多时候都意识不到约束的存在，比如主键、唯一索引、字段类型，换句话说，这是DBMS定下来的“规矩”，不可逾越<ul>
<li>经常有人用转账的例子来说明一致性，但感觉这并不是一个好的例子。“转账前后总金额不变”，真实中可能并不是DB的约束，而是业务系统的约束。</li>
</ul>
</li>
<li>ACID中的I，关注的是多个事务并发时的表现，一个事务是否能“意识”到其他事务的存在<ul>
<li>在Serializable级别下，所有事务都是隔离的、互不干扰，有点像as-if-serial（并不准确，as-if-serial指的是单个线程的行为）</li>
<li>但其实ANSI定义的Serializable并不准确。这种隔离级别下，虽然不会有常见的脏读/不可重复读等问题，但会有其他问题，比如write skew。所以ANSI的Serializable也可以被称作Snapshot Isolation（经典论文<a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-95-51.pdf" target="_blank" rel="external">A Critique of ANSI SQL Isolation Levels</a>）</li>
</ul>
</li>
<li>CAP中的C，核心在于“顺序”，类比java中的happens-before语义<ul>
<li>CAP中的C，其实跟DB没啥关系，甚至跟并发也没啥关系。所有系统中都要考虑到操作的顺序性。我先set a=10，然后再读a的值，我读到的是10么？这看起来像是废话，那是因为我们在用的很多系统中，隐式的保证了这个语义（一个线程中的每个操作，happens-before于该线程中的任意后续操作），在单机系统中也很容易做出这种保证。但分布式系统中，再引入并发的情况下，就没那么容易。</li>
<li>CAP中的C，严格指的是Linearizability，翻译做线性一致性。我的理解：对同一份数据的读/写都是原子的，并且按顺序（一般情况下是时间顺序）发生，后面的操作可以看到前面操作的结果。类比java中的volatile关键字。</li>
<li>具体到DB中的事务，其实指的是：如果某个事务写入的值对外界可见了，那后续所有的事务都能读到这个值（可能是那个写事务提交了，也可能是脏读之类）</li>
</ul>
</li>
<li>ACID中的I和CAP中的C，针对的其实是两种不同的特性，虽然跟并发都有点相关。二者结合才能完整的描述一个系统。比如我们大概可以说mysql默认是Repeatable Read + Linearizable的。同时满足Serializable + Linearizable的系统，也可以称作Strict Serializable，或External Consistency，也就是Spanner这种。</li>
<li>既然顺序如此重要，那如何决定操作的顺序？单机系统很容易生成一个全局递增的id，比如mysql中的事务id；zk虽然不是单机系统，但zxid的设计也是类似的。而在分布式系统中，更多是使用时间来决定。见Lamport大爷的经典论文：<a href="https://amturing.acm.org/p558-lamport.pdf" target="_blank" rel="external">Time, Clocks, and the Ordering of Events in a Distributed System</a>。<ul>
<li>如果有一个全局唯一的、准确的时间服务（比如Percolator中的中的TSO），那一个写事务完全可以选用当前时间作为事务id（或者说是数据的版本号），因为后续的读事务id肯定大于这个写事务，也就能读取到写入的数据，保证Linearizability会更容易</li>
<li>Spanner中的commit wait time，其实也是为了保证Linearizability，保证写入数据的版本号小于后续任意读事务的版本号，排除时间误差的影响</li>
</ul>
</li>
</ul>
<p>感觉上，似乎CAP理论中的三个维度，并不是true or false，而是0~100，不可能在3个方向上都达到100。<br>话说分布式领域，还有很多非常经典的论文值得一看，可惜精力有限，而且都比较偏理论，研究起来很花时间。</p>
<p>参考资料：</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzI4MTY5NTk4Ng==&amp;mid=2247489096&amp;idx=1&amp;sn=222de2e8729fd85d90bbc7529585f777&amp;source=41#wechat_redirect" target="_blank" rel="external">聊聊事务的30年发展历史</a><br>很赞，讲了各种隔离级别的历史问题，我以前总是搞不清SI和SSI。其中有一例精彩吐槽：</p>
<blockquote>
<p>也正是从此，大家开始意识到他们需要关心可串行性，一部分原因是 Jeff Dean 认为其非常重要。与此同时，持续呼吁该观点将近 20 年的 Michael Stonebraker 表示很无奈，大家显然只在 Google 明星工程师声明某件事的重要性时才会关心它。</p>
</blockquote>
<p><a href="http://kaiyuan.me/2018/04/21/consistency-concept/" target="_blank" rel="external">分布式系统一致性</a></p>
<p><a href="https://www.jianshu.com/p/3673e612cce2" target="_blank" rel="external">一致性模型</a><br>很赞，讲清楚很不容易。对pingcap还挺有好感的，很佩服能把这些玩的溜的人</p>
<p><a href="https://www.jianshu.com/p/8500882ab38c" target="_blank" rel="external">分布式系统的时间</a><br><a href="https://zhuanlan.zhihu.com/p/48782892" target="_blank" rel="external">[译] 分布式系统中的一致性模型</a></p>
<p><a href="https://www.zhihu.com/question/60278698/answer/305657878" target="_blank" rel="external">OCC和MVCC的区别是什么</a><br>感觉这个区别挺微妙的，别被那么多概念绕进去，按逻辑想想，很多东西大概就能明白了</p>
<p><a href="https://blog.csdn.net/chao2016/article/details/81149674" target="_blank" rel="external">强一致性、顺序一致性、弱一致性和共识</a><br><a href="https://www.jianshu.com/p/c348f68fecde" target="_blank" rel="external">隔离级别、SI 和 SSI</a></p>
<p><a href="https://blog.csdn.net/yyd19921214/article/details/68953629" target="_blank" rel="external">2PC与3PC的区别</a><br>很多人其实没有搞清楚3PC</p>
<h2 id="分布式共识">分布式共识</h2>
<p>提到分布式一致性，大多都会提到paxos/raft之类。但其实这是个翻译的锅，paxos中的一致其实是consensus，指的是“共识”；而ACID和CAP中的C，指的是consistency。paxos/raft应该称作分布式共识算法，解决的问题是“分布式环境下多个节点如何对某个问题达成一致”，区块链中的PoW算法也在此列。paxos和raft经常被拿来对比，却较少有paxos/zab的对比。</p>
<p>raft就不说了，这是我这些年看过的最通俗易懂的一篇论文，作者生怕你不懂，遣词造句都尽量简单。你需要的一切都能在论文里找到。而且raft就像kafka一样，有种简单但强大的感觉。<br>paxos就不一样了。首先论文本身就非常抽象，而且又缺少了很多细节，工程实现中也有非常多的坑。chuppy的论文中（没记错的话）也有吐槽：“工程中实现的paxos和原本的paxos，根本就是两个东西了，所以大多数系统都是运行在一个未经证明的算法上”。</p>
<p>一些关键点：</p>
<ul>
<li>对于basic paxos，理解paxos instance的概念非常重要。虽然论文里好像没有明确提出这个概念。简单点说一次表决过程就是一个instance。也正是在paxos instance概念之上，才能衍生出multi paxos（虽然Lamport大爷没有用这个词，全靠自己想象）</li>
<li>单次paxos instance的流程，是非常好理解的，如果我们不去关心它的证明。</li>
<li>如何生成提案号，论文中并没有指出。<ul>
<li>这个提案号不需要是全局有序的，甚至不用偏序，理论上随机生成都可以。。。就是提案通过的概率会很低而已。两个proposer完全可能生成一样的提案号，但只有一个能通过phase-1。</li>
<li>工程实现中，如果能保证全局有序，当然是最佳的，或者至少保证单个节点是递增的。一些文章会说将机器的IP作为提案号的一部分，比如高位放时间戳，低位放IP，这样某些机器的话语权会天生“较大”，不过算法的目的是尽快达成共识，也许这也不是问题。</li>
<li>chuppy的论文中，给处理了一个算法：s = 已知的最大值 * 节点个数 + 自身编号</li>
</ul>
</li>
<li>集群的各个节点状态是有可能不一致的，所以每次读也必须走一次paxos流程，这样才能保证读的一致性<ul>
<li>论文中也说：为了确定被选定的value，必须重新发起一次新的提案，这事一般是learner角色来做</li>
<li>也有这么说的：通过learner角色使所有Acceptor达到最终一致性。</li>
</ul>
</li>
<li>理论上来讲，一次提案不必发给所有的acceptor，提案发起者自己选定一个majaroity发送就可以了，所以各个节点的状态很可能是不一致的<ul>
<li>majaroity的概念需要准确理解，是整个集群的大多数，而不是活着的节点的大多数，所以成员变更是一件非常麻烦的事，Lamport大爷也没说成员变更的事，后续的<a href="http://read.seas.harvard.edu/~kohler/class/08w-dsi/mazieres07paxos.pdf" target="_blank" rel="external">《Paxos Made Practical》</a>进行了补全</li>
<li>只有收到majaroity的commit成功消息后，才能回复客户端</li>
</ul>
</li>
<li>活锁问题很好理解，如果所有节点都能任意提议。由此引出了leader的概念<ul>
<li>leader如何选举，并没有规定，可以通过paxos来选，但未必</li>
<li>leader还一般还要搭配lease机制，持续保持leader身份</li>
<li>而且竞选leader不需要有最新的状态，这点和raft很不一样</li>
</ul>
</li>
<li>multi-paxos并不是为了解决活锁问题，而是为了在某些场景下（单一proposer连续多次提交）提高效率，跳过prepare阶段，直接commit<ul>
<li>有些资料中提到leader必须先执行一段时间正常的两阶段流程，大概是为了catch up状态</li>
<li>状态catch up包括两部分：最大的instanceId，和日志重确认。因为新的leader状态很可能是落后的，而且命令序列中可能有“空洞”。这个时候leader其实扮演的是learner角色。</li>
<li>关于命令空洞的问题，论文中说的不是特别清楚，我觉得典型场景就是并发处理多个请求，后面的请求先成功，然后leader挂了</li>
</ul>
</li>
<li>理解paxos的关键在于，每个节点都是一个状态机，如何让各个状态机保持一致？<ul>
<li>最常见的机制就是State machine replication，也是Lamport大爷的经典论文，让所有节点回放相同的命令序列。这也正是multi-paxos做的事：在所有节点上确认相同的instance序列。</li>
<li>这是一个非常抽象的模型，可以用在很多地方，但每一个场景中都必须自己思考如何应用。Lamport大爷只提供水泥的配方，然后每个人开始自己造房子。</li>
</ul>
</li>
</ul>
<p>总的来说，建议还是不要直接看论文，太tm难懂了。应该先大概了解些paxos的概念再去看。而且各种关于paxos的文章太多了，不乏很多误导性的（我也不敢说自己的理解是对的），工程实现也是五花八门，还是直接看原文、看wiki比较好。</p>
<p>相比之下，zab就简单明确的多，估计很大程度上是因为有zookeeper这样一个“标准”实现。<br>zab和paxos的区别，<a href="https://cwiki.apache.org/confluence/display/ZOOKEEPER/Zab+vs.+Paxos" target="_blank" rel="external">官方wiki</a>中说的很清楚，前提是真的理解了paxos的状态机本质；zab论文的也道出了关键区别：<a href="https://ieeexplore.ieee.org/document/5958223" target="_blank" rel="external">《Zab: High-performance broadcast for primary-backup systems》</a>。我来尝试着解说下：</p>
<ul>
<li>paxos可能会对请求“重排序”的，只要没有处理过的请求（或者是说没有返回给客户端成功消息的请求），先后顺序无所谓，所有参与的节点，会“商议”出一个顺序。paxos只要最终形成的“instance序列”是一致的就可以了，所有的replica基于这个序列去回放，最终就能达到一致的状态。</li>
<li>而在某些场景中，是不能重排序的。能重排序的，往往是“外部client请求”；不能重排序的，往往是“增量状态更新”（incremental state changes）。这也就是state machine replication和primary-backup这两种机制的差别。</li>
<li>paxos也能用于primary-backup，但性能会差；而zab就是为这类场景而设计的，借鉴了paxos中的很多思想，比如两阶段、表决机制</li>
<li>zab中的所有写操作，是严格有序的（因果有序），所有写请求都由leader处理。leader生成递增的zxid，proposal会按顺序提交，tcp协议保证follower也是按顺序收到，follower保证按顺序回复<ul>
<li>这种设计必然会限制吞吐量，所以写性能堪忧（虽然我没看代码求证过），而且不能水平扩展。之前一篇文章<a href="https://mp.weixin.qq.com/s?__biz=MzI4MTY5NTk4Ng==&amp;mid=2247489041&amp;idx=1&amp;sn=b58745994c0c98662e2330c966b5036f&amp;source=41#wechat_redirect" target="_blank" rel="external">阿里巴巴为什么不用 ZooKeeper 做服务发现</a>也有提到这个问题。</li>
<li>我猜测，表决的时候不必一次次表决，可以一次表决多个事务，这样比较合理</li>
</ul>
</li>
<li>zxid还是一个挺巧妙的设计的，配合epoch概念，保证即使leader    切换，zxid也是递增的</li>
</ul>
<p>zab总体还是比较简单的，感觉上就像mysql binlog replication的威力加强版。。。但zookeeper不等于zab，zookeeper还是额外做了很多事情的。</p>
<p>此外常提到的算法还有<a href="https://dl.acm.org/citation.cfm?id=62549" target="_blank" rel="external">Viewstamped Replication</a>，据说和paxos本质上是一样的，时间上还要更早些。raft的论文中也有提到自己和VR/zab的区别。</p>
<p>还有个画风不太一样的算法就是<a href="https://en.wikipedia.org/wiki/Gossip_protocol" target="_blank" rel="external">GOSSIP</a>，常见的应用是cassandra和redis。据说可以在O(logN)的时间上收敛，但不会有一个“精确”的时间点，很好玩。</p>
<p>所有这些共识算法吧，你可以绞尽脑汁想各种异常状况，但还是难以覆盖，很难证明它是完备的，至少我的脑子是不行。。。</p>
<p>参考资料：</p>
<p><a href="http://dsdoc.net/paxosmadesimple/index.html" target="_blank" rel="external">【译】Paxos Made Simple</a><br>即使是paxos made simple，也没那么好懂</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MjM5MDg2NjIyMA==&amp;mid=203607654&amp;idx=1&amp;sn=bfe71374fbca7ec5adf31bd3500ab95a&amp;key=8ea74966bf01cfb6684dc066454e04bb5194d780db67f87b55480b52800238c2dfae323218ee8645f0c094e607ea7e6f&amp;ascene=1&amp;uin=MjA1MDk3Njk1&amp;devicetype=webwx&amp;version=70000001&amp;pass_ticket=2ivcW%2FcENyzkz%2FGjIaPDdMzzf%2Bberd36%2FR3FYecikmo%3D" target="_blank" rel="external">一步一步理解Paxos算法</a><br>英文wiki：<a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)#Basic_Paxos" target="_blank" rel="external">https://en.wikipedia.org/wiki/Paxos_(computer_science)#Basic_Paxos</a><br>中文wiki：<a href="https://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95" target="_blank" rel="external">https://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95</a></p>
<p><a href="https://blog.csdn.net/voidccc/article/details/39647787" target="_blank" rel="external">Paxos历史回顾</a><br>各种历史渊源讲的比较好</p>
<p><a href="http://oceanbase.org.cn/?p=90" target="_blank" rel="external">使用Basic-Paxos协议的日志同步与恢复</a><br>OceanBase大神的一系列文章，blog中除了paxos还有很多其他内容，非常赞</p>
<p><a href="http://paxos.systems/index.html" target="_blank" rel="external">http://paxos.systems/index.html</a><br>一个非常赞的站，集合了各种资料</p>
<p><a href="http://blog.fnil.net/blog/ac1fa10ff9b2404ed0b91bdfaf76a87d/" target="_blank" rel="external">分布式一致性论文阅读阶段性小结</a><br><a href="https://my.oschina.net/liangtee/blog/304779" target="_blank" rel="external">Multi Paxos：Basic Paxos的进化</a>：这里提到的队列策略我没太懂</p>
<p>各种paxos made系列：<br><a href="https://www.cs.utexas.edu/users/lorenzo/corsi/cs380d/papers/paper2-1.pdf" target="_blank" rel="external">Paxos Made Live</a>：google最让人印象深刻的就是强大的工程能力<br><a href="https://pdos.csail.mit.edu/archive/6.824-2007/papers/mazieres-paxos.pdf" target="_blank" rel="external">Paxos Made Practical</a><br><a href="https://www.inf.usi.ch/pedone/MScThesis/marco.pdf" target="_blank" rel="external">Paxos Made Code</a><br><a href="https://www-cs.stanford.edu/~matei/courses/2015/6.S897/readings/paxos-moderately-complex.pdf" target="_blank" rel="external">Paxos Made Moderately Complex</a></p>
<p><a href="https://www.jianshu.com/p/fb527a64deee" target="_blank" rel="external">Zab：Zookeeper 中的分布式一致性协议介绍</a><br><a href="https://www.cnblogs.com/mindwind/p/5231986.html" target="_blank" rel="external">Raft 为什么是更易理解的分布式一致性算法</a><br><a href="https://my.oschina.net/pingpangkuangmo/blog/782702" target="_blank" rel="external">Raft对比ZAB协议</a><br><a href="http://zookeeper.apache.org/doc/r3.4.13/recipes.html" target="_blank" rel="external">ZooKeeper Recipes and Solutions</a></p>
<p><a href="https://www.infoq.cn/article/consensuspedia-an-encyclopedia-of-29-consensus-algorithms" target="_blank" rel="external">30 种共识算法完全列表</a>：币圈真是群魔乱舞。。。</p>
<h2 id="其他">其他</h2>
<p>很多算法中都涉及到<a href="https://en.wikipedia.org/wiki/Leader_election" target="_blank" rel="external">选主</a>，这其实是一个很古老的话题，也经过很多研究了，比如<a href="https://en.wikipedia.org/wiki/Bully_algorithm" target="_blank" rel="external">Bully算法</a>。每个算法都可以定义自己的选主机制，比如zab中的快速选举，raft中的随机超时等。</p>
<p>研究的过程中，突然想到BT下载，这也是个挺神奇的东西。以前有tracker服务器的时候还好理解，磁力链是真的挺神奇的，各个节点是怎么知道数据块所在的位置的？虽然一直知道是所谓的DHT，以后有空再研究。<br>期间也接触了一些<a href="https://zh.wikipedia.org/wiki/%E8%BF%9C%E7%A8%8B%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98%E8%AE%BF%E9%97%AE" target="_blank" rel="external">RDMA</a>的概念，似乎是要专门的硬件？还找到了一个RDMA与HDFS集成的项目：<a href="https://github.com/Mellanox/R4H" target="_blank" rel="external">R4H</a>，不过已经废弃了。</p>
<p>参考资料：</p>
<p><a href="https://www.jianshu.com/p/f1659aba5aed" target="_blank" rel="external">https://www.jianshu.com/p/f1659aba5aed</a><br><a href="https://zh.wikipedia.org/wiki/%E7%A3%81%E5%8A%9B%E9%93%BE%E6%8E%A5" target="_blank" rel="external">https://zh.wikipedia.org/wiki/%E7%A3%81%E5%8A%9B%E9%93%BE%E6%8E%A5</a></p>
<h1 id="数据平台">数据平台</h1>
<p>老生常谈了。时不时我就会思考一下，数据的价值是什么，平台的价值是什么。</p>
<p>有段时间我经常以“xxx bigdata archtecture”为关键字在google中搜索，xxx可以是uber/airbnb/netflix/linkedin等等。一直以来各种分享、文章、架构图也看了不少，各种《大数据xxx》的书也看了不少。单纯说技术架构吧，大同小异，尤其各种小厂，毕竟有能力造轮子的还是少数。可能在规模、一些细节上有差异，但都不是本质的区别。那我们所做的事情意义何在？不就是搭个hadoop？（题外话，我很不喜欢这种“不就是xxx”的句式，这样说的人，要么非常资深，要么非常无知，而且无知的居多。比如我也可以说，所谓运营不就是拉新/转化/留存/召回，但这几个字背后是多少辛酸泪啊。。。）</p>
<p>平台 ≠ 各种开源组件的堆砌，同样的水泥，造出来的可能是陋室也可能是别墅。它的意义在于：</p>
<ul>
<li>赋能，我一直觉得这是所有大数据技术，甚至可以是所有技术的根本价值。它能拓宽你的想象空间，做到之前做不到的事。<ul>
<li>我一直说，我是造发动机的，也是卖铁锹的，工具本身不产生价值；或者说，它是整个价值链的最上游；</li>
<li>我能对外提供什么样的能力？可以是数据开发的能力，可以是可视化能力，可以是调度能力。能力也是有层次的：我们可以笼统的说，我们提供的是“洞察”数据的能力，是在数据中淘金的能力。然后再对这些能力进行细分/下钻，大概可以总结出一个能力模型吧。</li>
</ul>
</li>
<li>提升效率与稳定性。解决了“能不能”的问题，接下来就是“好不好”。毕竟基础组件就那些，能提供的能力也不会差太多。最重要的衡量标准就是效率和稳定，经历过烟囱式架构或者大单体应用的人，大概能够体会其中痛点。<ul>
<li>包括提升用户开发效率和降低管理成本两方面。更进一步，关注用户的体验与口碑。我喜欢讲的一个例子就是docker，它刚出现时只是把各种技术“黏合”起来，但就是极端好用，才有了后续的发展机会。广大人民群众就是这么耿直。。。</li>
<li>所以产品化是很重要，但也很容易被搞技术的人忽视的一个方面。</li>
<li>稳定不是靠运气的，而是靠系统化的建设，靠制度和规范的推进，需要自顶向下的规划</li>
</ul>
</li>
<li>与业务的契合度，这是每个公司之间最大的差别，不是所有经验都可以复制。<ul>
<li>大多数技术都会有一个支撑业务（满足现有需求） -&gt; 预见业务（满足未来需求） -&gt; 驱动业务（创造需求）的过程，尤其是各种新的技术。</li>
<li>我不光是个造发动机的，我还可以教你怎样用的更好。很多业务方自己是不够敏感的。</li>
</ul>
</li>
<li>数据文化，这就比较玄学了。我一直觉得数据能力应该是所有开发同学必备的能力，就像数据库/SQL一样，而不应该是需要专门培养的。甚至产品/运营也要具备这种能力，或者说这种思维。如何降低这个门槛，倡导这种文化，因环境而异。</li>
</ul>
<p>有用户问我，你们是不是中间件？仔细想了下，还真有点像。提到中间件，一般会想到RPC/trace/分库分表/配置中心/统一网关/MQ等等。和运维也有点像，运维=CMDB/DBA/nginx/发布平台/监控/GIT/持续集成等等。都是提供一些基础的“能力”，都是将一些公共的技术抽象出来。差别大概在于我们更偏上层一点，如果去驱动业务会更方便，毕竟数据是可以渗透到业务的整个生命周期的。很多所谓的平台，如果做的更偏业务一些，就会变成业务系统，比如分领域的用户画像。<br>什么时候我们能做到像分库分表中间件一样，让所有人能“下意识”的去使用，大概这事就成了。有那么点润物细无声的意境。</p>
<p>大多数平台也许大概是这么个发展路径：</p>
<ol>
<li>对需求从0开始支持。最开始的绝大多数需求都是报表，然后自己搭建hadoop、写各种ETL；</li>
<li>引入更多开源组件，开始各种排列组合；开始出现分工；</li>
<li>开始各种内部组件的开发，但大多是零散的点；对开源组件有限度的修改；快速的野蛮成长；</li>
<li>开始平台化 + 填坑；开始数据体系的建设，而不只是单纯的关注技术；出现更广泛的应用场景；开始关注功能之外的东西，比如治理；</li>
<li>对内大规模定制各种开源组件，整合各种需求，支撑并且驱动业务；对外做技术输出，持续提升影响力；</li>
<li>自研 + 公有云？</li>
</ol>
<p>一家之言。太长远的我也看不清。<br>这个分类也很笼统，目前来说，我们大概处于第四阶段吧。</p>
<p>说点题外话。价值这种东西，总是时不时困扰我，我相信其他人也是。我看到很多做“纯技术”的同学很焦虑，总觉得自己离业务太远；也看到很多做业务开发的同学很迷茫，觉得自己一直没有长进。这大概是一个永恒的话题吧，似乎每个人都在质疑自己在做的事的价值？那是不是可以推论所有的事情都没有价值。。。原来大家都是虚无主义的信徒啊。<br>我觉得吧，价值没有是否，只有难易。我们大多数的质疑，只是畏惧困难而已，人类天性可以理解。所以问题不在于有没有价值，而在于你自己是否愿意相信这个价值，在于如何让其他人也相信并且认同。如果我们自己不能笃定，也就没有资格回应别人的质疑。与其自己在角落怀疑人生，不如尝试去把它变成现实。</p>
<p>最近总是一不小心就开始灌鸡汤。。。</p>
<p>参考资料：</p>
<p><a href="https://item.jd.com/12385129.html" target="_blank" rel="external">《大数据平台基础架构指南》</a><br>给老板的书打个广告，没有太多细节，更多是宏观的指导，适合从业一段时间的人看</p>
<p><a href="https://help.aliyun.com/video_detail/88093.html" target="_blank" rel="external">DataWorks V2.0</a>：可以参考，很多在做的事情都类似</p>
<p><a href="https://dwhsys.com/2017/03/25/apache-zeppelin-vs-jupyter-notebook/" target="_blank" rel="external">Apache Zeppelin vs Jupyter Notebook: comparison and experience</a><br>相对于传统IDE的另一种交互方式，最近应用场景越来越多</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&amp;mid=2247484935&amp;idx=2&amp;sn=23252591e73d772379b5da59d8a381bb&amp;chksm=fbd4b46bcca33d7d2e6f83bd31c90f01cdc2642643b10bf3d8d0439639782754b3a0af57c737&amp;mpshare=1&amp;scene=1&amp;srcid=0529s3j9buN7E45gnlLMmti7#rd" target="_blank" rel="external">大数据平台架构从0到1之后</a><br>cluster+工具链+interface的分层，很特别；queryengine很赞，kylin玩的溜</p>
<p><a href="https://mp.weixin.qq.com/s/x35DiLcGrGasPoKXW_wLMw" target="_blank" rel="external">58大数据平台架构演进</a><br>技术演进过程（稳定性 -&gt; 平台治理 -&gt; 性能 -&gt; 异构计算）和我们很像<br>对于各种故障实例的分享很赞，有很多是我们真实碰到过的</p>
<p><a href="https://eng.uber.com/uber-big-data-platform/" target="_blank" rel="external">Uber’s Big Data Platform: 100+ Petabytes with Minute Latency</a><br>发展过程非常清晰，Incremental data modeling挺有意思</p>
<p><a href="https://medium.com/netflix-techblog/evolution-of-the-netflix-data-pipeline-da246ca36905" target="_blank" rel="external">Evolution of the Netflix Data Pipeline</a></p>
<h1 id="方法论和架构">方法论和架构</h1>
<p>方法论我也不是第一次吐槽了。为什么各种方法论这么蛋疼，一方面很多方法论是不讲逻辑的，或者有一些逻辑但是不自洽；但更多是因为不分场景的胡乱套用。虽然这不是方法论本身的锅，但管杀不管埋也不行吧。。。</p>
<p>大道至简，如果剥去所有包装，道理都是相通的，也是所有人都能理解的。儒学提炼下大概是“仁义礼智信”，心学提炼下大概是“知行合一”，xx宗教提炼下大概是“躺倒挨锤”（好像没什么不对）。这些文字大家都能读，但能领悟内涵的，很难；身体力行的，更难；想传达给其他人，难上加难。毕竟面对的是活生生的人，有思维、有智慧的人。人的理解总是有差别的，就像宗教的不同流派。为了防止这种分歧，又只能变得繁杂，对简单的概念附上各种“细则”去说明，真是矛盾。更蛋疼的是，有人总喜欢把简单的东西复杂化，演绎出一大套理论和术语。<br>如此说来，面对机器反而容易些。</p>
<p>这就和鸡汤文一样，道理都是对的，可能也确实是别人从自身经历总结而来的，但直接灌给你是没用的，必须你自己感悟出来。自上向下的指导各种思想，一般效果都不咋样，毕竟知易行难；往往要自下而上的总结出来。凭空指挥总是容易的，我能想出无数种可能性，每一种逻辑上都说的通，但求证的过程十分痛苦，而且未必有结论。<br>所以我对各种方法论都比较警惕，很难全盘接受，批判并吸收才是正途。</p>
<p>架构这个词，从某种意义上来说，也是方法论的一种，所以也会被滥用。架构是为了“更好的解决问题”，而不是为了架构而架构。各种抽象是真的有用，而不是为了抽象而抽象。大概是我最近看了一些反面例子，有些愤世嫉俗了。。。</p>
<p>解决问题的前提是发现问题，但很多人欠缺这个能力，我也是。<br>能力的成长不是天生的，也很难是别人灌输的，而是来自于不断的挫折与思考。除非你天赋异秉，洞察力超群，否则大多数“正常人”都只有经历过痛苦才能成长。有句话怎么说的，“未长夜痛哭者，不足与语人生”。</p>
<p>毕竟所谓的架构，如果放着不管，就会一直“腐烂”，就像热力学第三定律一样，无论当初设计的多么好。只不过腐烂的速度不一样而已，除非你的业务停滞不前。（业务倒逼技术升级，某些时候很无奈，但换一个角度来看，也是很幸福。。。）<br>大多数的系统，都是最初设计时，架构和需求非常match，然后不断的“打补丁”，最后实在补不上了，来一次重构。提前设计不是不可以，但小心变成过度设计。超前一步是天才，超前100步就是神经病了。如何在“现在”和“未来”之间平衡，如何在“架构”和“需求”之间平衡，会是永恒的难题。这可以说是技术的难处，也可以说是技术的魅力。<br>所谓架构师，就要思考更好的解决方法，即是“最佳实践”。不用最佳实践可以不？可以，但在可维护性、复用性、稳定性、扩展性上，就可能会有问题。<br>而且很多事情啊，都是粗略看很简单，但一细想就有很多问题。就说日志采集吧，h5/native、预聚合、降级、采样、设备标志、到达率。。。这些细节往往才是真正体现能力和深度的地方。</p>
<p>参考资料：</p>
<p><a href="http://www.infoq.com/cn/articles/alibaba-freshhema-ddd-practice" target="_blank" rel="external">阿里盒马领域驱动设计实践</a><br>非常赞，并不是DDD的无脑吹，让我真正理解了Persistence Ignorance的重要性</p>
<p><a href="http://www.infoq.com/cn/articles/architecture-thought" target="_blank" rel="external">优秀架构师必须掌握的架构思维</a><br><a href="https://mp.weixin.qq.com/s?__biz=MzI4MTY5NTk4Ng==&amp;mid=2247489005&amp;idx=1&amp;sn=91ee65614ee01ac01caf9f105d6575d2&amp;source=41#wechat_redirect" target="_blank" rel="external">怎么样的架构才是好架构？</a></p>
<p><a href="https://mp.weixin.qq.com/s/Dmymj3JpoEOt1MqGK5jNIQ" target="_blank" rel="external">PostgreSQL-51风控系统背后的利器</a><br>跟这哥们打过些交道，解决问题的思路很清晰，虽然他喜欢的postgre给我们带来很多麻烦。。。</p>
<h1 id="以人为本">以人为本</h1>
<p>很多时候，我经常在想，到底是因人成事还是因事成人，虽然实际情况必然是两个因素的混合。</p>
<p>但就我观察到的情况来看，大多数还是人的因素更占主导，尤其是小公司。<br>大公司也许可以靠各种基础设施、靠制度、靠流程，让那些不那么优秀的人也发挥作用，比如amazon。说的露骨一点，大多数人在这里就是螺丝钉而已。但在小公司，一件事情是否能做成，很大程度取决于这件事的owner是否足够优秀，是否有足够的推动力。换句话说，靠人的优秀来弥补基础的不足。毕竟从事情本身来说，大多并没有特别的前瞻性，很多时候是有经验可以借鉴，有路可以重复走的。<br>有点像法治 vs 人治的区别？</p>
<p>如果能源源不断招到优秀的人，很多问题都会迎刃而解。但毕竟这只是个理想状态。讽刺的是，大公司反而更容易招到优秀的人。<br>带领优秀的人做事总是更容易的，如何带领不那么优秀的人把事情做成，如何在管理成本很高的情况下把事情做成，才是管理能力的体现。“有多少人做多少事”，这不是一个很好的心态。我更希望是做“应该做的”、“正确”的事。从这个角度来说，事才是最终的目标，而人只是达成目标的一个手段，虽然是最重要的一个手段。</p>
<p>怎样才是优秀的人？我也一直在思考。我理解的码农的基本素养：</p>
<ol>
<li>技术能力。吃饭的家伙，但也只是个最低的门槛。</li>
<li>业务sense。这个标准就能刷掉一大波人。可以不直接参与业务，但必须要理解业务，知道如何产生价值。某些场景下，可以近似理解为产品意识。</li>
<li>价值观。很玄学，但真的有用。区别度很高。</li>
</ol>
<p>每一点展开都能讲很多。</p>
<p>价值观这个东西，也算是“恶名远扬”，我以前一直挺反感的。但我反感的是各种滥用和教条。<br>经历过的人越来越多之后，深深觉得“技术”只能决定人的下限，而“价值观”（或者说习惯/态度/方法）才能决定人的上限。而其中最重要的一条就是“积极”，或者叫自驱。不安于现状，主动思考如何做的更好，并能驱动整件事情的改进，碰到困难想办法解决而不是推诿，是否有不顾一切达成目标的心气。这些事情，说起来都很简单，但能做到的人真的是少数。很多人只是嘴上说着不甘于平庸，却不愿做出改变。<br>另外很重要的一条就是逻辑思考能力。虽然都是搞技术的，但很多人却欠缺这种能力。这世上虽然有很多不讲逻辑的事情，但庆幸的是大多数还是讲逻辑的，只是有些隐藏的比较深而已，只要稍微多想一点就能明白很多事情。</p>
<p>如何培养优秀的人？这又是一个很大的话题。<br>如果换个角度想想呢，作为个体，如何成长最快？必然是有个牛人带你，言传身教。影响人成长最大的因素是环境，和优秀的人在一起非常重要。出淤泥而不染不是不行，只是大多数人没这个素质，更容易变成“比烂”。如何建立一个良性的循环，让优秀变成一种习惯和氛围，一直都是难题。</p>
<h1 id="杂七杂八">杂七杂八</h1>
<p>各种奇奇怪怪的读后感。</p>
<p><a href="https://www.zhihu.com/question/22037987" target="_blank" rel="external">MPP 与 Hadoop是什么关系</a><br>所谓MPP，感觉是传统意义上的分布式数据库领域的？别纠结概念</p>
<p><a href="https://www.infoq.cn/article/2018%2F10%2Fpublished-gRPC-Web" target="_blank" rel="external">gRPC-Web 发布，REST 又要被干掉了？</a><br>脑洞真大，刚开始看到题目还以为js真的具备调用RPC的能力了，其实后面还是有个proxy的</p>
<p><a href="http://www.infoq.com/cn/articles/how-to-become-tech-master" target="_blank" rel="external">天天写「业务代码」，如何成为「技术大牛」？</a><br>来，干了这碗鸡汤</p>
<p><a href="https://mp.weixin.qq.com/s/j94s-jQjJ11zXSDD_uEcAA" target="_blank" rel="external">知识图谱的技术与应用</a>：例子讲的比较简单，想象空间还是挺大的<br><a href="https://mp.weixin.qq.com/s?__biz=MzI4MTY5NTk4Ng==&amp;mid=2247489030&amp;idx=1&amp;sn=9d5514051c88584ddde28f6be4094fce&amp;source=41#wechat_redirect" target="_blank" rel="external">一个支付服务的最终一致性实践案例</a>：比较典型的案例，类似BASE</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzA3MDg4Nzc2NQ==&amp;mid=2652136254&amp;idx=1&amp;sn=bba9bbd24ac8e5c1f6ef5d1125a6975b&amp;chksm=84d53304b3a2ba12f88675c1bf51973aa1210d174da9e6c2ddcd1f3c84ec7e25987b3bce1071&amp;mpshare=1&amp;scene=1&amp;srcid=1020GPmfbEVP9QDNlZBHg47I&amp;pass_ticket=vR63tjXqn9DTRAEWRKRMIB8O1ybqCamYyCza7%2BE5YRJRfpf%2F5OwphFKHGOiDiS6u#rd" target="_blank" rel="external">Service Mesh：下一代微服务</a><br>大多数公司连上一代微服务还玩不好吧<br>看了下似乎是将服务发现、通信等又抽象了一层？</p>
<p><a href="https://mp.weixin.qq.com/s/RuD4xNsr6DgEnyczB4_p9Q" target="_blank" rel="external">Bit Map在大数据精准营销中的应用</a><br>其实就是位图索引，但结合hbase做了很多事情<br>跟BloomFilter一样，都是非常精巧的数据结构</p>
<p><a href="https://zhuanlan.zhihu.com/p/30971463" target="_blank" rel="external">布隆过滤器在实时指标计算中的应用</a><br>常见的估算UV方法：BloomFilter和HyperLogLog，很多时候从业务层面也可以优化</p>
<p><a href="https://www.zhihu.com/question/20747381" target="_blank" rel="external">机器学习、优化理论、统计分析、数据挖掘、神经网络、人工智能、模式识别之间的关系是什么？</a><br>数据挖掘，也是一个很模糊的词，大概就是分类、聚类、预测、估算，还有NLP<br>或者说，机器学习指的是技术，而数据挖掘指的是业务目标？<br>这需要的不是多强大的算法能力，而是工程能力</p>
<p><a href="http://zaa.ch/jison/demos/calc/" target="_blank" rel="external">Jison</a>：很好玩的小工具，自动生成parser，有点像antlr</p>
<p><a href="https://zhuanlan.zhihu.com/p/35819123" target="_blank" rel="external">阿里技术参考图册</a><br>感觉PR的成分多一些，很多只是宏观的梳理，不过还是值得一看<br>话说梳理业务逻辑并不比技术简单，很佩服能梳理的头头是道的人</p>
<p><a href="https://www.infoq.cn/article/EE2bAVOOWa0K_g5lLh7j" target="_blank" rel="external">深度剖析阿里巴巴对 Flink 的优化与改进</a><br>我对flink其实不太熟，印象深刻的是FlinkSQL，摘录一段：</p>
<blockquote>
<p>我们发现有了Dynamic-Table之后我们不需要再创造任何新的流式SQL的语义。因此我们得出这样的结论：流式SQL是没必要存在的。ANSI SQL完全可以描述Stream SQL的语义，保持ANSI SQL的标准语义是我们构建Flink SQL的一个基本原则</p>
</blockquote>
<p>这还是挺特殊的，大多数流式SQL还是要新增一些语法的吧，google dataflow也没做到这一点吧。以后再研究下。话说Blink最近也要开源了啊。</p>
<p>说到流计算，前段时间看了<a href="https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101" target="_blank" rel="external">Streaming 101</a>和<a href="https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-102" target="_blank" rel="external">Streaming 102</a>，又看了<a href="https://qconsf.com/sf2017/system/files/presentation-slides/foundations_of_streaming_sql_qcon_sf_2017.pdf" target="_blank" rel="external">Foundations of streaming SQL</a>，一路按图索骥找到<a href="http://streamingsystems.net/" target="_blank" rel="external">《Streaming Systems》</a>（这本书一定要看动图，不然很难懂），看了几章总算开了点窍。因为我大部分对流计算的认知还停留在storm的年代，虽然很早就知道<a href="https://ai.google/research/pubs/pub43864" target="_blank" rel="external">Dataflow</a>和<a href="https://beam.apache.org/" target="_blank" rel="external">Beam</a>，但也没仔细研究过。等读完了要好好总结下，批处理和流处理的对比还是很有意思的。</p>
<p><a href="https://www.infoq.cn/article/the-evolution-of-large-scale-data-processing" target="_blank" rel="external">The Evolution of Large-Scale Data Processing</a><br>《Streaming System》最后一章的中文版，期待中文版上市</p>
<h1 id="END？">END？</h1>
<p>乱七八糟的写了这么多，我自己都觉得啰嗦了，不过写文字从来都不是目的，思考才是目的。<br>有人看了我的文字，说我挺纠结的，想太多。我也承认。<br>怎么说呢，有些人天生有成功的直觉，总是能凭直觉做出正确的选择；我好像还没显露出这种特质。。。所以还是多想想吧，总比浑浑噩噩的好，只是别因为思考而畏缩不前。<br>或者其实大家都在纠结，只是没有显露出来？</p>
<p>很多时候，有些问题我觉得已经想明白了，但过段时间再看，又糊涂了。。。我就是不断的在“好像懂了” -&gt; “好像还是不懂” 之间循环。<br>而且当你无所事事太久，一直看剧、刷新闻啥的，思考能力就会退化，思维会变得“扁平化”，缺少深度。深度来自哪里？来自于日常的思考和积累，从来就没有什么灵丹妙药，而且这可能是很痛苦的一个过程。<br>不过我发现一个现象啊，好点子往往都是洗澡的时候想出来的。为一个问题苦思许久不得要领时，往往能在某次洗澡时灵感迸发。。。虽然比不上阿基米德的“eureka”，但也让我很欣慰了。</p>
<p>现在最大的心愿就是下篇文章能写个“纯技术”的。。。但是吧太简单的（介绍类、tip类）写起来没意思，也没价值；复杂的吧，又需要很多时间研究。。。技术类的文章，要么就是对某个问题钻研的比较深，要说透彻；要么就是高屋建瓴，比较成体系。最关键的，还是要有自己的思考，copy-paste就没意思了。</p>
<p>这篇文字拖了这么久，可以说是忙，可以说是诸事缠身，不过这都是借口，其实就是懒吧。。。拖到最后，看着一堆要填的坑，真是头疼。不过写着写着就high了，吐槽还是挺爽的。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>又要开始胡言乱语了。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
      <category term="bigdata" scheme="http://jxy.me/tags/bigdata/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[北游记]]></title>
    <link href="http://jxy.me/2018/05/12/2018-qcon/"/>
    <id>http://jxy.me/2018/05/12/2018-qcon/</id>
    <published>2018-05-12T07:26:01.000Z</published>
    <updated>2018-05-13T13:29:34.000Z</updated>
    <content type="html"><![CDATA[<p>早就该写了，与之前的<a href="/2015/11/29/south-trip/">《南游记》</a>对应。</p>
<a id="more"></a>
<p>跑到北京参加了<a href="https://2018.qconbeijing.com/schedule" target="_blank" rel="external">QCon</a>，写写感想。这也是老习惯了，我对其他同学也是这样要求的，不管是参加什么规格的会议、看了些什么样的书籍、或者研究了什么新鲜玩意，一定要有总结或分享。这是梳理自己思路的一个过程，你的思考不一定是对的（毕竟人类一思考那啥就发笑），但至少证明有思考过。如果不去思考，很多道理即使摆在你面前你也不会发现，无论那道理多么浅显。<br>深度思考，真的不只是说说。</p>
<h1 id="还是大数据">还是大数据</h1>
<p>QCon其实并不是一个专门的大数据会议，而是综合性的。但纵观下来，大数据仍然热度不减。其实几个热门的主题大家都能想像到，按我自己的感觉，排名大概是这样的：</p>
<ol>
<li>AI/深度学习。没啥好说的，近几年最火热的主题，虽然我觉得有些泡沫，面临着和前几年大数据一样的问题：技术飞速发展，但炒作概念过多，落地很难。但总体还是看好的。</li>
<li>大数据。下文详述。</li>
<li>高并发/高可用/大规模架构。永不过时的主题。</li>
</ol>
<p>意外的是，没有看到太多区块链的分享。<br>同行的人感慨到，以前的QCon，讲讲系统、讲讲搜索之类的就可以了。现在不跟什么热门的概念搭上边，都不好意思上去讲。。。</p>
<p>大数据领域，虽然热度不减，但其实也有一些变化：</p>
<ol>
<li>从技术角度来讲，越来越规范了，或者说，同质化。以前那种百花齐放（或者说群魔乱舞？）的架构、方案，很少有了。也许已经进入了一个需要“微创新”的时代？</li>
<li>越来越实时。lambda architecture之类的，只是计算能力不足时的一种妥协。离线与实时的融合，是一个大趋势。</li>
<li>越来越多的SQL。尽量使用SQL来表达计算逻辑，已是业界共识。根本的好处在于便于优化执行计划，毕竟SQL已经研究了这么多年。但由于SQL不是图灵完备的，一些复杂的逻辑必须辅以UDF。</li>
<li>越来越偏向于应用。我不是第一次吐槽“落地太难”了，但感觉大家已经慢慢摸索出了一些套路，成功落地的案例越来越多了。说的更大些，是数据文化的落地。而且我们以前所理解的“应用”还是太狭隘，数据无处不在。</li>
</ol>
<p>如果按所谓的<a href="https://zh.wikipedia.org/wiki/%E6%8A%80%E6%9C%AF%E6%88%90%E7%86%9F%E5%BA%A6%E6%9B%B2%E7%BA%BF" target="_blank" rel="external">技术成熟度曲线</a>，也许是行业正在慢慢成熟吧，应该是好事。话说每次看到这个曲线，我总是想到生物课上的<a href="https://baike.baidu.com/item/%E7%BB%86%E8%8F%8C%E7%94%9F%E9%95%BF%E6%9B%B2%E7%BA%BF/8888040" target="_blank" rel="external">细菌生长曲线</a>。。。</p>
<h1 id="部分干货">部分干货</h1>
<p>只写写印象比较深刻的几个，我也不会完整复述每个演讲的内容，只是吐吐槽。ppt都在QCon官网上，可以自己去看。</p>
<h2 id="京东的spark_on_k8s">京东的spark on k8s</h2>
<p>与Intel合作的。怎么说呢，脑洞真是大。。。话说科技的进步就是来源于脑洞，要敢想，关键人家能把脑洞变成现实。大跃进式的放卫星就算了。</p>
<ul>
<li>来自Databricks。已经随spark 2.3正式发布。</li>
<li>k8s的好处就不说了，在业务系统中其实已经应用的很多了。</li>
<li>本质是为了异构计算，统一的抽象所有的GPU/CPU/存储等资源。更多的用于算法训练、深度学习之类。<ul>
<li>并不能替代传统的hive+MR，定位不一样。</li>
</ul>
</li>
<li>但为什么不是用YARN来做这些事呢？我知道的业界一些做法是增强YARN的docker支持，然后在YARN上跑tensorflow/caffe之类。<ul>
<li>讲师的一个解释是YARN双层调度的性能损失，这无法否认。但影响面有多大不知道。</li>
<li>另一个可能的原因是和业务系统一样统一用k8s管理，降低运维成本。</li>
</ul>
</li>
<li>传统观点来看，数据本地性是个大问题，但既然是定位于算法、训练，根本就不用care数据本地性。取决于你的workload类型。</li>
</ul>
<h2 id="阿里的MaxCompute">阿里的MaxCompute</h2>
<p>MaxCompute，就是原odps，在业界也算很知名了。当初大数据刚开始兴起时，BAT各家都有自己的自研框架，似乎只有阿里坚持下来并发展到如今规模，还打赢了内部pk（分别起源于阿里云和电商的两套体系），这份魄力，佩服。<br>也可能是其他两家发声太少？至少baidu当初是大张旗鼓搞过很多东西的，自研的BigTable什么的。</p>
<p>本次相关的分享有两个，分别是MaxCompute和它对应的运维体系。有点不够接地气，这也没啥办法，太复杂而且全都是自研。<br>话说去年云栖大会的时候我也去听过MaxCompute，讲师也是同一个人。</p>
<ul>
<li>自研就是好，可以做各种深层次的优化：索引、缓存、文件格式甚至指令级的优化。</li>
<li>对于各种应用场景和痛点的整理，非常有价值。</li>
<li>HBO印象深刻，大家也都认可它的必要性，但不知道咋做的。。。</li>
<li>profileing也是印象深刻，也是不知道咋做的。</li>
<li>运维体系超复杂，比被运维的系统还复杂。</li>
</ul>
<h2 id="链家网的数据平台">链家网的数据平台</h2>
<p>对于这个分享，用一个词来形容就是“扎实”。没有黑科技，没有令人眼花缭乱的概念和包装。就是讲构建数据平台的过程中碰到的各种问题和解决方法，从规模和技术上都和我现在面临的情况最像。可惜ppt没放出来。</p>
<ul>
<li>了解清楚业务和用户，比了解技术和架构更重要，尤其对于中小公司。<ul>
<li>印象深刻的是，讲师很清楚自己的数据有哪些（人和房）；也很清楚自己的数据会被怎么使用（BI/to C/to B等等）；我羞愧的低下了头。。。</li>
<li>这也是业务模式决定的。链家的业务模式注定数据量有限，或者说上限比较低。毕竟房子总不可能9.9包邮。</li>
</ul>
</li>
<li>技术上的问题，倒是都差不多。但链家的平台化起步更早一些。</li>
</ul>
<h2 id="实时专场">实时专场</h2>
<p>这个专场比较有意思。总共4场分享，分别来自于实时三大流派：databricks的spark streaming、阿里的flink（最近blink很火）、pinterest的Kafka Stream（虽然我觉得跟前两者不是一个量级），还有一个比较另类的来自于美团（下文详述）。三大流派同场争锋，场下的观众们也是煽风点火：“spark streaming相对于flink有哪些优势？”，“为什么要选择flink，而不是上一场讲到的streaming？”，很好玩。可惜的是，这个专场的ppt大多都没有放出来。</p>
<ul>
<li>说是三大流派，其实我觉得flink会一家独大。<ul>
<li>kafka就不说了，优点在于简单，缺点也是简单。</li>
<li>spark的优势在于批处理和流处理可以部分共用同样的逻辑，但在SQL语义下，这个优势正被弱化。</li>
<li>spark的另一个优势是推广做的好，但也正被其他人追赶上来，尤其是blink这种有阿里站台的。</li>
<li>后发优势了解一下？</li>
</ul>
</li>
<li>本来以为会讲spark 2.3新出的continous streaming，结果只是一笔带过。<ul>
<li>提到了一个datatricks delta，正在尝试融合离线与实时，感兴趣可以了解。</li>
</ul>
</li>
<li>阿里的ppt非常赞，讲了实时处理中的各种问题：chandy lamport、retraction、exactly-once等，可惜没放出来。<ul>
<li>同样也是缺少细节，很多东西只是大概提了下。</li>
</ul>
</li>
</ul>
<h2 id="美团的SnappyData">美团的SnappyData</h2>
<p>非常另类。这个分享来自于美团的酒店团队，是一个纯业务团队。它展示了业务团队是如何看待大数据相关技术、如何确定自己需求、如何选型的，这个思考过程值得深思。在三大流派下场后，安排这样一个分享也许是别有深意：对业务方而言，争论各种引擎的优劣毫无价值。这个演讲的ppt放出来了，我建议每一个数据团队的同学都去仔细看看。</p>
<ul>
<li>通俗点说，SnappyData就是一个内存数据库。美团的解决方案就是所有数据放内存，然后用SQL去做adhoc。<ul>
<li>内存数据库不是个新鲜东西，但在大数据场景下蛮少见。</li>
<li>我提了个问题，如果要查大量数据，比如一年，内存放不下咋整？答案是他们仔细评估过，一年的数据量也不会有多少。。。而且机器成本换人力成本很值。</li>
</ul>
</li>
<li>也许在“内行”看来，这个解决方案很不完美。我们早期也是各种东西乱用：mongo/hbase/cassandra/greenplum之类，导致现在很痛苦。但业务团队就是这样思考的，我们有必要去理解。</li>
<li>平台 vs 业务，通用 vs 专有，长期 vs 短期，想取得平衡很难。“在正确的阶段做正确的事”，说起来很简单，道理大家都明白，但没有通用的解法。</li>
</ul>
<h2 id="数据应用">数据应用</h2>
<p>上面我也说过，大数据领域越来越重视应用和落地。我们以前所谓的“应用”更多是“在业务上产生价值”，也就那么几种方式：BI、推荐、风控、算法之类的。但其实很多其他领域也是会用到数据的，比如运维（滴滴《数据驱动的风险防范体系建设》），比如测试（腾讯《手Q性能优化的大数据实战》）。本次会议甚至有一个“大数据下的软件质量建设实践”专场。虽然吧，很多在我看来只是跟大数据沾个边，而且很多只是T+1的报表或统计（这在大数据领域是要被鄙视的），但真的能产生价值。只能说以前还是太狭隘。。。</p>
<h2 id="杂七杂八">杂七杂八</h2>
<ul>
<li>几场海外的演讲，感觉都收获不大，更像是请来镇场子的。一个java的，讲了些Java9和Java10的新特性；一个kafka的，讲了些kafka的历史和一些基本特性。其实这些东西，随便搜一下就能知道的。</li>
<li>kotlin和go的两个演讲，主要关注于协程。这种纯语言特性的分享，还是挺少见的，可惜这两种我都不会。。。</li>
<li>阿里的微服务实践，题目很大但落点比较小。在线btrace印象深刻。</li>
</ul>
<h1 id="鸡汤时间">鸡汤时间</h1>
<p>写文章要首尾呼应啊。</p>
<p>参加这种会议，会接触各种各样的技术，看到各种各样的点子，但切忌头脑发热。不能落地的idea，都是YY。有一个词：buzzword-driven architecture，就是指这种情况，看到什么新鲜的东西都想拿过来。很多东西可以借鉴，但真的是无法复制。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>早就该写了，与之前的<a href="/2015/11/29/south-trip/">《南游记》</a>对应。</p>
]]>
    
    </summary>
    
      <category term="bigdata" scheme="http://jxy.me/tags/bigdata/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[玄之又玄]]></title>
    <link href="http://jxy.me/2018/04/01/xuan-zhi-you-xuan/"/>
    <id>http://jxy.me/2018/04/01/xuan-zhi-you-xuan/</id>
    <published>2018-04-01T14:09:57.000Z</published>
    <updated>2018-04-06T14:05:26.000Z</updated>
    <content type="html"><![CDATA[<p>太多的事情想不明白。</p>
<a id="more"></a>
<p>敲着键盘，想写些东西，耳机里放着《空空如也》，脑袋也是空空如也。<br>很多时候我想要某个“答案”，却连问题是什么都说不清。<br>所以答案是<a href="https://www.zhihu.com/question/19846530" target="_blank" rel="external">42</a>么？</p>
<h1 id="A-side">A-side</h1>
<p>我还是想不明白技术和业务的关系，或者说，做平台系统和做业务系统的关系，但至少我去想了，也去尝试了。</p>
<p>工作关系，我过去做的比较杂。说说做业务系统是什么感觉：风险与收益都会更大，面对的情况瞬息万变，很锻炼抽象/架构/创新能力，前提是你的业务真的能活下来并做大。过去两年的热点我基本都蹭过一些：直播、AI、电商、内容，但运气不好，做一个死一个，没啥沉淀，越做越慌。</p>
<p>与之相反，做平台系统会更“纯粹”，更“通用”，初期成长会非常快。如果能打下一个坚实的技术基础，对未来的职业发展有巨大的好处。问题在于很多时候能一眼看到“上限”，想在这个上限上做些突破，很难。很可能只是一些零零碎碎的修补，更可能是不断重复已有的经验——比如把大公司的轮子不断在小公司复制。</p>
<p>就像是钱去炒股和存余额宝的区别。很可惜，我是赔了本的那批。。。</p>
<p>对个人而言，要么向下“沉”，成为committer/contributor。当一个Oracle ACE（比如姜承尧大神），似乎也不错；要么向上“浮”，更多的去贴近业务。<br>但我过去就是摇摆不定，时而上浮时而下沉。。。</p>
<p>其实吧，二者间没有那么明显的界线，灰色地带很多，这个问题也不是一个简单的二元论。而且在不同的公司文化、不同的场景下，答案也会不同。孤立的去看待任何一方，都是不对的。关键要找到一个平衡的“点”，如何结合，这才是每个人都需要思考的。说的装逼一点，这是战略层面的考量。而且也要考虑个人兴趣，你是想去屠龙，还是想打造屠龙刀？没有屠龙刀，屠龙就无从谈起。用水果刀屠龙不是不行，关键是龙答应么？</p>
<p>忘了从哪看的，说在淘金热的时候，真正发财的不是淘金者，而是卖铁锹的，技术也许就是扮演着这样一个“卖铁锹”的角色。有那么点“小确幸”的意思。</p>
<p>但有一点是确认无疑的：结果导向。公司/团队/个人想要的结果是什么？没拿到结果，什么技术/业务都是空谈，只能收拾心情，总结下经验教训，从头再来了。至于这个结果是什么，如何让所有人都认同，那就是另外的话题了。如果期望的结果是赚钱，那就最简单了，搞房地产去啊。</p>
<p>说回大数据领域。大数据技术本身是没什么业务意义的，很少有直接面向C端的产品，更多的是作为支持性的内部平台而存在。我<a href="/2015/03/24/some-thoughts/">以前</a>（居然是三年前。。）就说过：落地太难。或者说，太多的人不知道怎么使用数据，缺少一种“下意识”。不同的公司需要不同的做法，<strong>跟业务场景强相关，而且成功的经验往往不可复制</strong>。但我觉得它未来一定会是各个业务的核心。因为业务的发展一般有一个趋势：拼产品 -&gt; 拼技术 -&gt; 拼运营。在业务野蛮成长的时候，大家都不会去重视技术，更遑论重视数据，毕竟随便挖一铲就有金子，谁还去操心那些细节；在竞争对手逐渐增多，产品逐渐同质化后，大家会开始重视技术，谁的产品更快，用户体验更好，谁就能圈更多的地；在技术也逐步同质化后（比如现在的电商or搜索or直播），就是拼运营的时候了，拼同样大的地谁的亩产更高，而数据往往就是运营的关键，有点像漫灌和滴灌的区别。这里的“运营”不是狭义的做个活动之类的，而是更广义的“达成目标”。靠拍脑袋做运营的不是没有，但那一般是老板才有的特权。。。数据驱动，这个口号可能有点虚，就像现在大家都叫着“未来是AI的时代”，但没有人知道那到底是怎样的一个时代，需要做些什么。但如果能见证这个由模糊到清晰的过程，做出一些微小的贡献，也算幸莫大焉。<br>借用一句话：技术（不只是大数据）总是短期内被低估，长期内被高估。这就是业务的发展趋势所致吧。</p>
<p>这会带来一个“问题”：大数据在小公司（或者说是小的业务线）会天然的不受重视。这也挺正常的，我要是业务方我也这样。。。但都说屁股决定脑袋，我现在既然是这个领域的从业者，就要去思考怎么去体现出价值，说到底，这是所有infra团队共同的迷思。不光是大数据技术的价值，还有数据本身的价值。我常用的一个比喻：技术（hadoop/spark）是发动机，而数据才是汽油。虽然我现在是个造发动机的，但我承认真正的价值在于数据。而且如何造发动机是一个问题，如何让发动机和你的车匹配又是另一个问题，否则三轮车配个12缸发动机，也不太合适啊。说到底这都是一个系统性的问题。作为底层从业者，要在正确的时间做正确的事，这很难，不光需要自己有想法，也需要看清技术发展的趋势，看清公司业务发展的趋势，还需要得到外部的支持和肯定。</p>
<p>一些凌乱的想法：</p>
<ul>
<li>趋势：离线与实时的融合，这是整个大数据领域的趋势，Databricks尤其热心</li>
<li>赋能：调度/批处理/流处理/即席/数据交换/ML/DL</li>
<li>统一的入口和DSL：SQL</li>
<li>开放平台与服务化：as a service</li>
<li>选型：抱着spark的大腿</li>
<li>分层：最基本、也是最有效的设计思路</li>
</ul>
<p>话说，大数据领域，如果不是作为内部平台，有哪些出路？要么卖技术：比如上云，就像去年TiDB上腾讯云一样；要么卖数据，用户为数据付费，而不是为技术付费，比如很多to B的数据产品；要不就是卖咨询、卖解决方案之类的。</p>
<p>说到做平台，还有一个很重要的原则：场景化。必须想好用户是谁，怎么用。先有场景，再有价值，然后再去评估价值的大小与代价。而不是做出的东西没人用，只是空中楼阁。各种平台和中间件尤其容易出现这种情况。很多平台需要的不是什么hard core的技术，而是对整个体系的理解、规划、设计与资源整合的能力。</p>
<p>参考资料：</p>
<p><a href="http://www.infoq.com/cn/articles/integrate-data-analysis-platform" target="_blank" rel="external">如何整合复杂技术打造数据分析平台</a>：可以看出真的是经历过痛苦和思考的，而不是单纯YY，有些话不是经历过的人讲不出来。</p>
<p><a href="http://www.infoq.com/cn/news/2018/01/meizu-data-architecture" target="_blank" rel="external">魅族数据平台的设计哲学和核心架构</a>：技术上没什么亮点，但有句话说的挺好：”数据平台并不那么显山露水，很多情况下甚至就像一座冰山，人们看到的只有那么一角”，所有infra都是这样，所谓“神人无功，圣人无名”。</p>
<h1 id="B-side">B-side</h1>
<p>越努力，越焦虑，诚如斯言。</p>
<p>与其踟蹰不前，不如先做些“正确”的事，片刻不敢松懈。因为我能看到太多优秀的人。知道的越多越痛苦，有那么点克苏鲁的意思。短短几年，身边的人已经遥不可及。曾经有一道选择题，“你愿意做快乐的猪，还是痛苦的苏格拉底？”。虽然这个题目有些逻辑谬误，并不是只有这两个选项，但我不想变成前者，虽然我有点怀念那个换个新手机就能高兴半个月的年岁。</p>
<p>有时我总是会羡慕别人的人生，我当然知道有失偏颇，我看到的或者说在意的只是比我好的，但还是忍不住。话说，人生这种东西，真的可以规划么？它更像是一个个偶然的叠加。都说选择比努力更重要，都说机遇比实力更重要，都说脚踏实地仰望天空，我们读了那么多的大道理，却还是过不好自己的生活。这些道理旁人可以引导，却不能灌输，非得自己体悟出来不可。多少人看到某条哲理便如获至宝，觉得简直是人类灯塔，下半辈子就指望这条哲理活了，然而转眼就忘掉了。</p>
<p>大道至简。老子五千言，几张A4纸就能写下，又有多少人能读懂，读懂了又有多少人能去实践。好比圣人们真的把大象装进了冰箱，但只告诉我们“分三步”并记录成典籍，少了太多的细节和思考过程，剩下的只是荒诞和可笑。为什么不能有更多的记录呢？难道圣人们在故意为难我们？大概是因为说了也没用吧，这必须要每个人自己去领悟。山顶只有一个，上山的路却有多条。当你真的站到山顶，会发现所有的知识/道理都是融会贯通的，什么道家/儒家/心学，所求的都差不多。</p>
<p>虽然从小受着辨证唯物主义的教育，但我发现有些事情真的是唯心的：你相信自己能成为什么样的人，就会成为什么样的人；很多看起来很棘手的问题，当你下决心去解决，抛开一切，从全局思考，似乎就会变得简单。这是一种很奇妙的感觉。</p>
<h1 id="END">END</h1>
<p>话说回来，虽然是“玄之又玄”，虽然有太多的事情想不明白，但真的有那么多事情要去想明白么？遇事不决莽一波，可能是更好的选择。</p>
<p>这个blog本来应该是技术布道的，最近却慢慢有变成神棍/鸡汤的倾向。。。难道真的老了。<br>anyway，我写的都是我想写的，这就够了。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>太多的事情想不明白。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[驽马十驾]]></title>
    <link href="http://jxy.me/2017/10/01/who-am-i/"/>
    <id>http://jxy.me/2017/10/01/who-am-i/</id>
    <published>2017-10-01T04:36:56.000Z</published>
    <updated>2017-11-27T18:35:12.000Z</updated>
    <content type="html"><![CDATA[<p>好久没写，拖延症又发作了。<br>另一方面，最近刚换了工作，各种事情还是挺头疼的，此间曲折难与人言。</p>
<a id="more"></a>
<p>换工作这种事呢，也不是第一次经历，没有那么多感慨了。但总归是一个比较大的变动，也有了一些新的体会与想法。整个过程虽称不上一帆风顺，但秉承着一贯的革命乐观主义精神，还是有些东西值得说道说道的。经历和见识有限，博君一笑罢了。</p>
<h1 id="面试感想？">面试感想？</h1>
<p>为啥会想到换工作呢？我出去面试的最大感受，就是很难说清我过去两年做了什么。。。这就是问题所在了。</p>
<p>虽然我一直吐槽自己是“<a href="/2017/04/02/lazy-busy-lazy#关于业务">打杂工程师</a>”，啥都干，对很多领域都有些了解。但从个人成长来说，这未必是好事。有我的原因，有团队的原因，也有公司的原因。在“打杂”的过程中，想要去沉淀一些什么，真的很难。如果说是技术上的沉淀，业务都没起来何谈技术架构？只会慢慢变成一个crud boy；如果是业务上的沉淀，隔个几个月就换个业务方向，又能有啥深入的思考？</p>
<p>这种情况下，面试时就会很被动。我的技术基础还可以，所以一般一面都能聊得很开心，跟面试官天南海北的瞎扯。我也挺喜欢这种感觉的，就像技术交流一样，平时工作中真的很难有这种机会。<br>但后续的面试中，往往会涉及一些很“大”很“虚”的问题。包括你对业务的理解，对架构的思考和规划，甚至是方法论。有次面试官直接让我谈谈对DDD（领域建模）的理解。。。如果是我年轻的时候，估计会觉得这特么都是扯淡，就像魏晋玄学一样，清谈而不解决实务。确实谈论这些很容易变成嘴皮功夫、变成ppt（某司的核心竞争力），但这些东西是真实存在并且有用的，前提是自己真的有经历过、思考过，而不是看了一些文章、一些书籍便到处吹嘘。当一个人在谈论这些的时候，分辨他是否有真材实料，也是个技术活。</p>
<p>某次二面，从头到尾都没有问一点技术细节，就是讲系统是如何设计的，我说一些细节甚至会被阻止。我举了自己之前在做的一个社区系统的例子，被问的十分狼狈。说来惭愧，社交领域真的没咋思考过，这个系统也只是为了应付功能。如果是大数据、分布式相关领域的，我还能扯上两句。说到底还是做的事情太杂太浅，不够深入。结果不出意外的挂了。<br>事后反思，这块其实有很多事情可以讲的。风控、服务化、结构化、数据挖掘、个性化推荐。。。但之前从来没有想过，所以这波挂掉也不冤。</p>
<p>出去面试之前我曾联系过一个猎头，大致介绍了下我的情况。他说我之前搞大数据，现在做业务开发，如果还做业务想拿到好的offer可能比较难。当时我不以为然，觉得他很外行，都是相通的嘛。没想到他一语成谶。。。略显讽刺的是，最终还是凭大数据相关的经验拿到了不错的offer，又滚回去搞数据平台了。。。换言之，我这两年到底有没有成长。。。不过也没啥好抱怨的，路都是自己走的嘛。</p>
<p>面试中的另一个问题在于我只敢说自己确定了解的一些事情，一些了解不多的领域都会很谨慎，这其实也是有些吃亏的。对于那些很“虚”的问题，适当的吹水还是有必要的。要给人一个侃侃而谈的印象，不要有不确定的观点，不要犹豫。有些时候太实在不是好事，说到底还是程序员思维作祟。<br>话说技术分享也是一样的，看看别人的题目：系统稳定性保障核武器、高可用架构演进之路、海量数据场景下解决方案。。。看着就不明觉历有木有，气场就不一样。</p>
<p>此外，面试中经常碰到的一个情况：对面是一个不懂你领域的高P，尤其是职位不是特别“对口”时。这种时候一般着重考察技术之外的能力，比如逻辑、沟通、深入思考、领导力。有些面试官会引导你，问你细节，然后不断深入；而另一些人则会直接把你做的事贬的一文不值，抹杀所有努力，典型句式：“不就是xxx么”，言下之意你这个太简单太low。。。这种时候我就有点被动了，不知道该怎么接话茬。我会尝试说服对方，但很多时候连说服的机会都没有，“这个太简单，换个话题”。我曾尝试跟某个面试官解释hadoop，对方直接一句为啥不用oracle就把我噎回来了。。。我也懒得再浪费口水了。我不知道这是一种面试策略还是怎样，还是单纯取决于面试官个人性格？当然我承认确实有些人是站得高看得远，可能在他们看来确实是low，就像我们带新人一样。。。但这其中的真真假假又如何区分？也许只是我运气太差？</p>
<p>话又说回来，表达能力真是很重要，如何把自己在做的事情说的高大上，尤其是对于外行，每个人都应该仔细思考。“做牛逼的事，讲给傻逼的人听”，确实如此。</p>
<p>总的来说我还是挺喜欢面试的感觉的，能让我看到自己的不足，这已是万幸了。如果一直沉浸在已有的小圈子中，很容易变成温水煮青蛙。有句话说的好，人受憋屈武艺高，跳出舒适区去外面看看，还是很有必要的。</p>
<p>P.S. 面试过程中感觉没怎么碰到算法题啊，唯一碰到的一道题是kSum，我特么还没答出来。。。其实这个题也是很经典了，在leetcode上做3Sum/4Sum之类的我也写过通用的解法，但面试中状态不佳也没啥好说的。。。</p>
<p>P.P.S. 面试中遇到了一些“话术”，像是销售一样。。。一般就是各种挑你的错误，不断的打击你，但这种套路一旦识破了就很容易出戏，就像一个销售对另一个销售使用话术一样。。。</p>
<h1 id="十字路口">十字路口</h1>
<p>我之前说自己中年危机，还是戏谑的意味多一些。但这次换工作的经历，让我觉得自己正站在一个十字路口上，必须做出一些很重要的改变。</p>
<p>对于年轻人，只要技术基础好、态度积极、踏实肯干、听话就可以了。一直以来无论我在哪家公司，招聘应届生、工作一两年的人，标准差不多都是这样。但对于我这种老年选手，就没那么简单了。这也就是我一直在强调的“沉淀”的重要性。</p>
<p>你要明白如果公司招你进去，对你的“期待”是什么。如果只是简单的crud，干嘛要招你这么贵的一个人，找个应届生培养个两个月，不是一样的效果？又不是皇亲国戚。。。是要你带起某块业务？是从头搭建技术架构？是看中你的经验还是资历？总不会是看中颜值吧，又不是孙红雷。。。要思考对公司的“价值”是什么。想清楚这些东西，以后的路才好走，不至于浑浑噩噩的混日子。</p>
<p>从个人发展的角度，我大概想了下：</p>
<ul>
<li>要有自己的“领域”。也可以说是自己的护城河、核心竞争力，一个意思。通俗点说就是在某个方向上要能有深入的理解，要能别人所不能，这样才能体现自己的价值。所谓的领域，可能是技术的，可能是非技术的。一个典型的例子就是现在的算法工程师，随着AI的火热，身价也水涨船高，但算法的壁垒还是比较高的，如果之前没研究过想进入这个领域还是挺难的，就算进入这个领域想达到一定的高度也要旷日持久的投入，这就是他们的“护城河”。另外总有些人会拿“深度”、“广度”来说事，说自己“深度”不行，“广度”还可以之类的。但这只是块遮羞布而已，你在某个深度上证明自己之前，没人在乎你的广度。</li>
<li>更多的关注人，而不是事。换言之，<strong>be social</strong>。在某个年纪之前，只要埋头做事就可以了。但如果还想持续保持竞争力，就要更多的关注人。不说八面玲珑，至少要能融洽相处吧，如果要带团队，更是如此，要清楚每个人的性格，擅长/不擅长什么，才能合理安排。对我这种脸盲症中期患者而言，还是有点难度的。。。隐居的高手什么的，那都是扯淡，现在哪有单打独斗做事的，都是靠团队冲锋。有人会拿多隆举例子，说他一心钻研技术取得什么什么成就。但多隆只有一个，而所谓的技术宅到处都是，性格乖僻无法合作的我也见过不少。还有很多人以乔布斯为偶像，但没学到乔布斯的才华，只学到了他的刻薄和偏执。反正我看到的混得好的人，都是非常擅于处理人际关系的。</li>
<li>有自己的想法，别怕发言，能独当一面，<strong>自带体系</strong>，不要等别人指挥。因为越向上走，接收到的“指令”往往越模糊，可能只是一句话。要把这句话落地实现，跟拿着详细设计文档写代码，完全不是一个层次的事情。必须要自己去思考如何实现，全权的own这个东西，很多事情要主动的牵头和推动。关键在于做事的意愿，也可以叫做野心、aggressive、匪气。说到匪气，有时候工作真的和土匪一样，太书生气是要吃亏的。。。生活中亦是如此，<a href="https://zh.wikipedia.org/wiki/%E8%8B%8F%E4%BA%AB%E8%8C%82%E8%87%AA%E6%9D%80%E4%BA%8B%E4%BB%B6" target="_blank" rel="external">善良又胆小的人没有活路</a>。</li>
<li>职责没有边界。所谓小富即安：搞好我自己这一块就可以了，其他的天塌下来也不关我的事。这种想法挺常见的，但对于自身的长远发展其实是不利的。</li>
<li>严禁追求完美。这是很多程序员的通病，总是想找一个完美的解决方案。领先别人一步是天才，领先十步就是神经病了。同理，思虑周全是好事，但导致瞻前顾后不敢行动就是SB了。我们当然要思考长期的解决方案，但很多时候我们要先拿出一个短期的“补丁”，才有资格去思考中长期的事情。</li>
<li>尽量去做核心业务，去接触核心的技术、核心的圈子。毕竟扎马步之类的练得再好，也只是基础动作，也比不上九阴真经里的一招半式。。。关键在于开拓“眼界”，这是很抽象但是也很重要的一个东西。好比谈到创业，有些人只能想到学校门口开个炒饭大排档，有些人却可以写商业计划书去骗风投。。。而且不同的圈子里，能接触到的人也不一样，从其他人身上学习往往是一条捷径。</li>
</ul>
<p>乱七八糟写了这么一通，感觉还是说的不清楚。。。就是隐隐的有一种感觉，但很难写出来，所谓的“升咖”的机会。</p>
<p>话说回来，其实很多时候都是因事成人，一个人的成功不是因为能力如何出众，而是做对了某件事。但如何在恰当的时机把握住机会，就是成功者区别于常人之处了。君子善假于物，此言不虚，顺大势而行方是正途。你可以说是狗屎运，但不得不服。</p>
<p>最后摘录一段鸡汤：</p>
<blockquote>
<p>在某个年纪之前，你可以靠透支身体、小聪明和老天给你的运气一直取巧地活着。然而到了某个年纪之后，真正能让你走远的，是自律、积极和勤奋。</p>
</blockquote>
<h1 id="还是架构">还是架构</h1>
<p>架构这个东西，就像盲人摸象一样，每个人都有自己的理解。架构说到底就是“做某件事的方法”。这个东西不能没有，但是也不能过于纠结。我也说不清楚，但我经历过一些反模式，倒是可以说说。</p>
<p>这两年参与了各种业务，每次一有新需求，开发们就开始叫，这个做不了，那个改动太大。。。然后开始和产品撕逼，要求砍需求，或者评估一个很长的开发时间，让产品们知难而退。。。就算最后迫于压力接下需求，也是各种吐槽。<br>但仔细想想的话，有些需求是合理的，反而是技术架构设计的不好才导致各种问题。产品说我要做一个能发140字短文本的社区，好的技术们去实现了；后续产品们又要求能发图片，但上一个版本的设计只能支持文字，于是又是各种大改。。。但这个问题开始的时候就应该想到啊。这就像代码里的hard code一样，既然当初偷了懒，欠下技术债务，给自己埋了坑，踩坑的时候就别抱怨。</p>
<p>过于短视，只关注眼前的一些需求，只关注一些“点”，缺少全面的思考就可能导致各种问题，因为大家都抱着一种事不关己的态度，反正我把分给自己的需求做完没bug就行了。。。<br>正常来说要先有个通用的架子，然后结合需求和产品的未来规划来做设计。</p>
<ul>
<li>尽管有许多架构都能满足功能需求，但其中只有一少部分能满足品质需求，比如可变性、性能、容量、安全</li>
<li>在设计的过程中，有许多“决定”要做，未必是架构师来做决定，有些决定是“隐含”的</li>
<li>越早期的“决定”，影响越大</li>
<li>越早期的阶段，修复错误就越容易</li>
<li>不要让架构“自动出现”。切忌不断的“堆日常”，最后只会让整个系统失控。</li>
</ul>
<p>一个好的架构通常也是有些套路的：</p>
<ul>
<li>关注状态。可能是整个系统的状态，也可能是一行数据的状态、一次调用的状态。也可以说是生命周期、链路。画个状态图，把整个状态变化的过程捋顺了，很多时候就能看出有哪些扩展点，就像AOP一样。</li>
<li>分层。所有的架构都有一个根本的目标，就是解耦，分层是最简单的手段。层与层之间的交互尽量清晰。</li>
<li>关注元数据和配置。它们都可以用于增加系统的可变性和扩展性，可以适当使用DSL等，像规则引擎之类的，但也不宜过度。</li>
<li>关注各种非功能性需求，像是监控、SLA、安全等等。即使很长时间内都没有这种需求，初始设计时也必须考虑到。</li>
<li>参考已有的架构。一些已经有最佳实践的领域，可以直接借鉴。更重要的是注意各种反模式，就像代码里的“坏味道”一样，一旦出现类似的“不和谐”的感觉就要注意了。</li>
</ul>
<p>具体到大数据这块，我感觉我现在就是个造发动机的。。。数据平台的职责在于向下屏蔽各种基础设施（hadoop/spark/kafka之类），向上提供友好的、业务相关的UI和API，提供各种使用数据的能力。在它上面会有数据仓库、各种数据驱动的应用等强业务相关的系统。所以感觉它就是个发动机，数据就是汽油。最差的架构就是一堆基础设施，直接暴露给用户，没有一点平台的概念。就像暴露一堆“裸设备”给别人用，这就是为啥要有文件系统。<br>一个不太恰当的比喻：就像NewSQL一样，我们应该提供sql给用户使用，而不是直接丢一堆low-level API给他，说你自己玩去吧。。。</p>
<p>P.S. 关于业务和技术，我现在还是觉得应该是以业务为导向的，即使现在搞平台相关，离业务稍远。这是一种“思考方式”，脱离业务需求去单独的研究技术是很危险的：不注重业务意义、不注重与人交流、搞各种奇技淫巧。一个反例就是，我曾经研究过一些guava中函数式编程的接口，然后用到了自己的系统中，这tm就是给后来的人埋坑，我保证他们看不懂。。。业务是应该驱动技术的，应该从业务的角度去思考，我要用技术去做哪些事。</p>
<p>P.P.S. 既然说到了奇技淫巧，我发现很多程序员都有这个坏习惯：喜欢在正式项目里“练手”，把简单的东西写的很复杂，看到一些新奇的东西就强行用在项目里，还喜欢自己造轮子，最后都没人维护。。。所以说，好的代码总是相似的（有最佳实践），烂的代码则是千奇百怪。</p>
<h1 id="END？">END？</h1>
<p>年纪渐长，心中总是难免有些感慨。好比以前听歌听周杰伦，“为你翘课的那一天，教室的那一间”；现在开始听李宗盛了，“岁月你别催，该来的我不推，走远的我不追”。<br>现在我的很多文字，如果给几年前的我看，估计会被吐槽“这谁啊这么矫情”吧。。。有很多事情，还是希望自己能早点明白。很多道理不是不知道，但只有亲身经历过才会懂，这真是个矛盾。我已经过了为赋新词强说愁的年纪了（大概），但幸好吐槽的功力还保留着几分。</p>
<p>有时候也会想，生活&amp;工作咋就这么复杂呢，就不能只是简单的吃着火锅唱着歌么。。。但不管你喜不喜欢，现实如此。不想被麻匪绑走的话，还是努力些吧。<br>似乎我从来不是一个聪明的人，旁人对我的形容多是“踏实”。驽马十驾，功在不舍，聊以自慰吧。</p>
<p>一些文章：<br><a href="http://geek.csdn.net/news/detail/235751" target="_blank" rel="external">http://geek.csdn.net/news/detail/235751</a><br><a href="https://zhuanlan.zhihu.com/p/27891140" target="_blank" rel="external">https://zhuanlan.zhihu.com/p/27891140</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>好久没写，拖延症又发作了。<br>另一方面，最近刚换了工作，各种事情还是挺头疼的，此间曲折难与人言。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[分布式大杂烩]]></title>
    <link href="http://jxy.me/2017/06/03/distributed-mess/"/>
    <id>http://jxy.me/2017/06/03/distributed-mess/</id>
    <published>2017-06-03T11:27:44.000Z</published>
    <updated>2017-06-30T12:27:54.000Z</updated>
    <content type="html"><![CDATA[<p>这算是老本行？终于暂时爬出了前端的大坑。。。</p>
<a id="more"></a>
<p>最近<a href="https://en.wikipedia.org/wiki/NewSQL" target="_blank" rel="external">NewSQL</a>的概念挺火的，似乎是<a href="https://github.com/pingcap/tidb" target="_blank" rel="external">TiDB</a>又带了波节奏。看到新名词总是想去研究下，感觉就是Spanner+F1的开源版？就像hbase之于BigTable，hadoop之于GFS+MapReduce一样。<br>但Spanner好些年前（2012）就出了啊，为啥最近才火起来。不过话说回来，GFS的论文也是2003年出来的，但直到hadoop 0.2x（2009年之后）才算真正有了成熟可用的开源版本。</p>
<p>其实前些年火的词是“NoSQL”吧，后来却慢慢不怎么听到了。突然跳出一个NewSQL，还是有点意外的，果然是又续上了么。。。那我也来+1s吧。正好我对分布式系统中的各种概念还有点模糊，顺便研究&amp;总结下。本文正如其名是个大杂烩，不是教程，想到哪写到哪。。。一家之言，仅供参考；如有错误，撒手不管。<br>毕竟这是很大的一个领域，很多人投入无数精力去研究，而我只能从自己接触比较多的系统入手去总结，管中窥豹而已。</p>
<p>感觉上，NewSQL就是NoSQL + 关系模型 + SQL + ACID么。</p>
<p>另外，严格来说，MapReduce、<a href="https://zh.wikipedia.org/wiki/%E8%A8%8A%E6%81%AF%E5%82%B3%E9%81%9E%E4%BB%8B%E9%9D%A2" target="_blank" rel="external">MPI</a>、dubbo之类的也是分布式系统，甚至<a href="http://folding.stanford.edu/" target="_blank" rel="external">Folding@Home</a>、<a href="https://setiathome.berkeley.edu/" target="_blank" rel="external">SETI@Home</a>之类的也算是分布式系统，不过这个离我们有点遥远，我们主要关注的还是各种分布式存储系统。</p>
<p>话说，我觉得NoSQL/NewSQL之类的更像是营销词汇，给人一种“RDBMS已死，NewSQL当立”的感觉。。。这些词跟bigdata一样，没有一个严格的外延，所以不要太纠结，更重要的是关注各个系统的原理/架构/适用场景。</p>
<h1 id="单机存储_&amp;_NoSQL">单机存储 &amp; NoSQL</h1>
<p>其实这节的标题本来叫做“一切从RDBMS开始”，后来想想可能有点片面。<br>各种单机存储已经发展的很成熟了，很多思想无论是单机系统还是分布式系统，都是相通的。《圣经》说的好：“太阳底下无新事”，很多时候我们都只是在重复前人。</p>
<p>研究一个存储系统，不管它是单机的还是分布式的，我们要从哪些方面入手？</p>
<ol>
<li>数据模型：Key-Value？关系型？</li>
<li>对外的API：其实很多时候API是由数据模型决定的</li>
<li>数据的存储方式：哪些数据在内存？哪些在磁盘？格式？</li>
<li>读路径/写路径：搞明白如何读/写一个数据，就会对系统有一个大概的认识</li>
<li>容灾、扩展、维护之类的：运维相关的特性，如何做到HA，如何做到可伸缩等等</li>
<li>最后才是一些细节：是否有啥优化措施，适用场景，一些特殊的算法&amp;数据结构等等</li>
</ol>
<p>关于数据模型，NoSQL时代有很多人总结过了，借用下：</p>
<ol>
<li>文件型（包括对象存储）</li>
<li>关系型：比如mysql/oracle</li>
<li>文档型：比如mongo</li>
<li>KV型：这个就多了，很多昙花一现的项目（原因就是KV系统实现上相对简单），目前比较坚挺的似乎就是redis。严格来说zk是不是也是KV？</li>
<li>BigTable：比如hbase。话说我一直觉得从存储方式上来看，BigTable其实也是KV，只不过多了个scan操作。</li>
<li>其他异类：比如neo4j这种基于图模型的。</li>
</ol>
<p>一般来说，单机存储的标杆就是RDBMS，我们实际中用的最多的就是mysql，它会作为衡量其他系统的一个“基准”。研究一个系统时，很多时候都会和mysql做对比，比如是否支持完整的事务特性，是否支持索引，是否有schema，join的方式（Sort-Merge Join/Hash Join），执行计划的优化（RBO/CBO/谓词下推）之类的。</p>
<p>关于事务，再多说几句（基于InnoDB），这可以说是RDBMS最重要的特性。事务的关键在于并发，所以很多概念跟基于线程的并发模型（比如java）是相通的。简单点我们可以认为一个事务就是一个临界区（critical section）。<br>java中同步的原则是啥？保持和顺序一致性模型的执行结果一致（<a href="https://jcp.org/en/jsr/detail?id=133" target="_blank" rel="external">JMM</a>中的as-if-serial语义）。同样，事务间的同步，目的也是为了事务并发执行时，和事务串行执行时，结果是一致的，这就是Serializable隔离级别。但实际应用中，我们一般会牺牲一些隔离性的要求来换取性能提升，于是就有了Repeatable reads、Read committed等隔离级别。</p>
<p>隔离级别可能导致的问题：脏读/不可重复读/幻读/丢失更新，这都是老生常谈了，但很多资料没有说清丢失更新有两类。第一类丢失更新：两个事务同时修改一个数据项，但后一个事务中途失败回滚，导致前一个事务已提交的修改丢失；第二类丢失更新：两个事务同时读取和修改同一个数据项，后面的修改可能使得前面的修改失效。</p>
<p>在java中我们是如何同步线程（保护临界区）的？锁和CAS。同样，事务的同步机制也可以简单分为基于锁的和基于MVCC的（其实<a href="https://en.wikipedia.org/wiki/Concurrency_control" target="_blank" rel="external">并发控制</a>是很大的一个领域，有很多方法去实现同步，这两种只是最常用的）。<br>大部分单机数据库的事务都是基于锁的，可能有一些MVCC的特性（比如InnoDB的非锁定一致性读，真是拗口。。。）。而分布式数据库中MVCC基本是标配，当然也有结合锁一起用的。</p>
<p>不过数据库中的锁似乎比较特殊，可能需要遵循一种<a href="https://en.wikipedia.org/wiki/Two-phase_locking" target="_blank" rel="external">两阶段锁</a>协议，简言之就是：1. 所有的读写操作都必须加锁；2. 所有获取锁的动作，必须发生在所有释放锁的动作之前，比如<code>Lock A; Lock B; Unlock A; Unlock B;</code>就是遵循这个协议的，而<code>Lock A; Unlock A; Lock B;  Unlock B;</code>就是不遵循的。数学上可以证明，只有所有事务都遵循这个协议，无论系统如何调度，都可以实现串行化隔离级别。<br>事务还有个优点就是死锁检测，会自动检测到死锁并回滚代价最小（undo最少）的事务。而java中如果死锁，只能重启。</p>
<p>InnoDB中的锁还有个特殊的地方，虽然它是行锁，但它其实是锁在索引上的。在某些情况下它可能锁单行（比如根据主键更新），但更多的时候它会锁一个区间（锁整个B+树节点），这被称作间隙锁（Gap Locking、Next-Key Locking），被用于防止幻读问题。所以如果事务操作时没有涉及索引，是可能导致锁表的。</p>
<p>至于MVCC，也被叫做乐观锁。这只是一种思想，可以有不同的实现。比较常见应该是<a href="https://en.wikipedia.org/wiki/Timestamp-based_concurrency_control" target="_blank" rel="external">Timestamp-based Concurrency Control</a>，使用时间戳作为版本号。<br>InnoDB中的MVCC只是读数据时不用加锁而已（基于undo实现），其实并不是完整的MVCC。</p>
<p>说到MVCC，还不得不提到一个“<a href="https://en.wikipedia.org/wiki/Snapshot_isolation" target="_blank" rel="external">快照隔离级别</a>”，Snapshot Isolation，简称SI。这并不是一个标准的隔离级别。因为<a href="http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt" target="_blank" rel="external">SQL-92标准</a>中定义的4种隔离级别都是针对使用锁的事务而言的，不太适用于使用MVCC的数据库。感觉上是比serializable稍微弱一些的隔离级别。还有更进一步的<a href="https://wiki.postgresql.org/wiki/SSI" target="_blank" rel="external">Serializable Snapshot Isolation</a>，简称SSI，其实就是serializable。</p>
<p>题外话，不同数据库的隔离级别定义很混乱的。。。可能A数据库的RR在B数据库里就是serializable，还有的数据库直接管SI叫做serializable，别太纠结。</p>
<p>另外，虽然ACID的概念已深入人心。但其中的C在不同的地方却有不同的解释。在单机RDBMS中，“一致性”被解释为“在事务前后各种约束不被破坏”，比如一个int型的字段不能变为long的，各种触发器该触发的也必须被触发，unique的字段不能有重复值之类的。但是在分布式系统中，“一致性”就未必是这样了，很多情况下意味着“事务写入的数据是否立刻能被后续的事务读到”。关于CAP的很多争论其实也源于每个人对一致性的理解不同，原文“A service that is consistent operates fully or not at all”，感觉上更像是说对外部而言，一个操作要么发生了要么没发生，不存在中间状态。</p>
<p>说回NoSQL。对于NoSQL而言，一般有几个特点：</p>
<ul>
<li>schemaless：还记得一有新需求就要改表结构的黑暗时期么。。。</li>
<li>反范式：不过说实话即使很多OLTP也是反范式的，关键看需求</li>
<li>不支持完整的ACID</li>
<li>有限的索引支持：最重要的标准是“是否支持二级索引”。我以前一直奇怪为啥叫做二级索引，那有没有三级、四级索引？后来才知道，这是相对于RDBMS中的聚集索引（Clustered Index）而言的，Secondary Index翻译成“辅助索引”更好一点吧。。。不会那么容易误解。</li>
</ul>
<p>NoSQL一般都要非常小心的选择使用场景，用起来其实不是很方便，每种NoSQL系统一般只能在自己的“最佳实践”里才能发挥作用。而且对系统的侵入性很大，因为缺少一个统一的API层（JDBC之类的）。在业务建模时也必须要从NoSQL角度出发去思考。我以前见过直接把RDBMS的思路套到hbase上，一建表就搞十几个列族那种。。。</p>
<p>题外话，有种说法是“hbase支持单行事务”，这其实指的是BigTable中单行数据的读写是原子的，hbase中并没有显式的事务概念。了解这个特性非常重要，因为很多基于BigTable的事务方案都是在这个特性上做文章。<br>如果用ACID的标准去衡量hbase：<a href="https://hbase.apache.org/acid-semantics.html" target="_blank" rel="external">https://hbase.apache.org/acid-semantics.html</a></p>
<h1 id="常见套路">常见套路</h1>
<p>总结下各种分布式系统中的常见套路。就像前面说的，很多思想和单机系统是相通的。</p>
<h2 id="WAL_&amp;_Checkpoint">WAL &amp; Checkpoint</h2>
<p>WAL（Write Ahead Log）可以说是大多数分布式系统的基石。</p>
<p>WAL本质就是一种日志（log），也有叫做message/journal的。通俗点说，WAL就是操作记录，在写数据时，先写日志，再写实际的数据，当二者都成功后才通知客户端写操作成功。这其实不是什么新鲜概念，在所谓的<a href="https://zh.wikipedia.org/wiki/%E6%97%A5%E5%BF%97%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F" target="_blank" rel="external">Journaling file system</a>中早就有类似的做法了。mysql中的binlog，InnoDB中的redo log，严格来说也都属于WAL。</p>
<p>在单机系统中，WAL一般用于系统崩溃后的恢复。比如InnoDB中写数据后只更新内存中的buffer pool，如果在脏页刷新之前系统崩溃，没有刷新到磁盘的数据就丢失了。这时就可以使用redo log来恢复数据了。hbase中也有类似的用法。<br>当然WAL也有其他用法，比如InnoDB中的redo跟事务的原子性也有关（commit record）。</p>
<p>但在分布式的环境下，WAL有一个更重要的作用：维护整个系统的一致性。它不仅仅是紧急时刻用来恢复数据的一个“备用手段”了，而是系统的核心。原理也很简单：同样的原始数据，经过<strong>相同</strong>的操作序列后，会得到同样的结果。而操作序列就是用一连串的WAL来表示的（必须是有序的）。换言之，在同一份数据上回放同样的日志。最常见的场景就是维护主从节点间的数据一致，比如mysql replication。各种分布式一致性协议（paxos/zab/raft），也都是在WAL有效的前提下才成立的。</p>
<p>hdfs中的editlog就一种WAL，通过共享操作日志，使ActiveNN和StandyNN保持状态一致，实现HA；hbase中直接就叫做WAL了，但只是用于数据恢复，跟一致性没啥关系，因为hbase将数据一致性“委托”给hdfs了；redis中的replication，是通过所谓的“命令广播”去实现的，虽然不是基于日志，但思想是类似的；redis的AOF持久化也是同样的道理，持久化的其实是一系列操作日志。<br>spark中的failover也有类似的思想：某个节点挂掉后，它不会尝试恢复那个节点持有的数据，而是从原始数据重新transform一次。</p>
<p>WAL还有一个额外的好处：写日志一般是顺序写的，很少有随机读写。在使用机械磁盘的情况下，这个特性非常重要。但也要看具体的实现，比如InnoDB中的redo文件就不一定是顺序写的，因为除了要在文件尾部追加，还可能要更新文件头部的一些元数据。</p>
<p>理论上来说，WAL可以无限增长。但实际中这显然是不可能的，磁盘空间是一个原因，更重要的是如果不限制WAL的大小，系统崩溃时回放WAL可能就要很久，很长时间无法对外服务，那就没什么意义了。所以WAL一般会结合所谓的Checkpoint机制来控制日志的大小。首先要为每条日志生成一个唯一的id，InnoDB中称作LSN（Log Sequence Number），hdfs中称作txid，hbase中称作Sequenece Number。触发checkpoint后，某个id之前的所有修改会全部持久化，小于这个id的日志就可以丢弃了。</p>
<p>触发checkpoint一般有几种情况：</p>
<ol>
<li>定时触发（InnoDB中的MasterThread，HDFS StandbyNN中的StandbyCheckpointer）</li>
<li>日志数量达到某个阈值：比如超过x条日志未合并</li>
<li>日志大小达到某个阈值：比如日志量达到xx MB</li>
</ol>
<p>WAL也是有局限性的，no silver bullet，可能引入一些其他的问题：</p>
<ul>
<li>多了一次写操作，效率上肯定会有影响。InnoDB使用一个redo log buffer来优化性能，写日志时先写buffer，然后异步刷新磁盘。但这又会带来可靠性的问题。</li>
<li>WAL保证数据的可靠性，谁来保证WAL的可靠性？理论上来说WAL应该是写入后立即持久化的，但很多时候我们会为了性能做一些妥协。要是每写一次数据就调用一次flush+fsync，虽然这是最保险的办法，但这系统也基本上可以定义为“不可用”了。。。很多时候我们会把选择权交给用户，比如InnoDB可以让用户配置何时刷新redo log buffer。如果想要最大的安全性，就要配置为每次事务commit都刷新并fsync。<ul>
<li>共享存储：保证可靠性的另一个方案是使用共享存储，将日志的可靠性交给其他人去保证。比如hdfs中的journal node、hbase中基于hdfs实现的WAL，或者直接用kafka。这会带来一个额外的好处就是日志与系统解耦，即使Master所在的机器挂掉也不会影响日志，对于维护数据一致性有很大好处。</li>
</ul>
</li>
<li>临界状态。一个很简单的问题：一个写操作，写日志成功了，但真正写数据前机器挂掉了。那对客户端而言，这个写是成功了还是失败了？要不要重试？这其实不是WAL的问题，就算写数据成功了，但向客户端返回数据的时候也可能因为网络原因导致客户端没有收到。（我就搞一个防火墙规则过滤你返回成功信息的数据包，你能咋地。。。）只不过使用WAL时写操作不是原子的，这个问题可能更严重。这个时候其实客户端处于一种“两可”的状态，看系统的设计思路。不过一般选择都是重试，这就要求使用方保证操作是幂等的。（题外话，关于幂等：日志的回放不一定是幂等的，看设计，但幂等会有很多好处。InnoDB中的redo因为记录的是数据页的变化所以是幂等的，但binlog就不是幂等）。</li>
</ul>
<p>为啥在各种分布式系统的架构中kafka这么重要？很大程度上是因为它提供了一种可靠、有序的日志分发手段，这是它和传统mq最大的不同。想用传统的mq做日志分发也可以，但会有各种限制，比如顺序问题。kafka就像润滑剂一样，我<a href="/2015/10/12/hadoop-application-architectures/">以前</a>就说过，“设计系统碰到困难时，试着扔个kafka进去”。说的更专业些，它提供了数据集成（Data integration）的能力。就算不用kafka，自己开发中间件，最后肯定也会演化成类似的东西。<br>关于日志，有一篇经典的文章：<a href="https://github.com/oldratlee/translations/tree/master/log-what-every-software-engineer-should-know-about-real-time-datas-unifying" target="_blank" rel="external">The Log: What every software engineer should know about real-time data’s unifying abstraction</a>，但这篇文章里所讨论的log是更广义的，整个data pipeline都是基于log的，或者叫做“log-centric architecture”。</p>
<p>参考资料：</p>
<p><a href="https://www.zhihu.com/question/30272728/answer/56373661" target="_blank" rel="external">https://www.zhihu.com/question/30272728/answer/56373661</a></p>
<h2 id="replication">replication</h2>
<p>为了保证数据的可靠性一般都要使用数据冗余机制。在单机的情况下，我们可以使用RAID；在分布式的场景下，一般有两种方式：多副本（replica）和<a href="https://en.wikipedia.org/wiki/Erasure_code" target="_blank" rel="external">纠删码</a>。</p>
<p>多副本很好理解，也是最常见的一种冗余方式，就是一个数据copy多份，典型的比如hdfs的3副本，对读可以做很多优化（优先读某个副本/本地读），问题在于也需要3倍的磁盘空间。纠删码比较少见，但这个东西很有意思，大意就是将一组数据分成n个数据块+m个校验块分别存储，只要丢失的数据块小于m个，那数据总能完整恢复。优点在于节省空间，但是数据恢复时会比较耗cpu和带宽。<br>似乎各种开源项目里很少见到纠删码，大多是基于副本的。估计只有各个云存储厂商才会使用吧，毕竟可以节省成本。。。接下来的讨论也都是关于副本的。</p>
<p>说到副本，就不得不提到一致性。这方面的资料已经很多了，最终一致性的概念也已经深入人心，但大多数资料没说明白：在分布式系统中，“一致性”一般分两种。</p>
<ol>
<li>副本间的一致性，保证各个副本的数据是同步的</li>
<li>整个系统的一致性，或者说是事务的一致性，不能有中间状态，比如经典的转账例子</li>
</ol>
<p>我们常说的ACID中的一致性指的其实是2，paxos/raft之类的分布式一致性协议针对的其实是1。很多人讨论一致性时会把两种情况混淆起来，就会很难理解。<br>顺便提一句<a href="https://zh.wikipedia.org/zh-hans/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4" target="_blank" rel="external">两阶段提交</a>（2PC），它和paxos/raft的目的是不一样的。2PC可以适用于上面说的两种场景，理论上用来保证副本一致性也可以，但一般没这么用的，更多的还是用于事务的一致性。<br>接下来讨论的都是针对副本的一致性。</p>
<p>强一致性/弱一致性的概念就不赘述了。强一致性还有两种：<a href="https://yeasy.gitbooks.io/blockchain_guide/distribute_system/problem.html#带约束的一致性" target="_blank" rel="external">顺序一致性和线性一致性</a>。最终一致性是弱一致性的一种特例，最终一致性又有读写一致性、会话一致性、单调读、单调写等变种（其实这种说法<a href="https://en.wikipedia.org/wiki/Consistency_model#Client-centric_Consistency_Models.5B19.5D" target="_blank" rel="external">并不准确</a>）。不要太纠结概念。很多概念看着吓人，说白了其实很简单。<br>一致性的问题其实很常见，只是很多我们没有意识到而已。比如多核CPU间要保持cache的一致性（缓存嗅探）、同一个CPU中不同级别cache也要保持一致性。最终一致性也不是新鲜东西，DNS、CDN、各种缓存，反正各种带有TTL的系统，基本都是最终一致性。</p>
<p>保证副本一致性的方法被称作<a href="https://en.wikipedia.org/wiki/Consistency_model#Consistency_Protocols" target="_blank" rel="external">Consistency Protocols</a>，学术界已经研究了很多年了。大体上可以分为两类：</p>
<ul>
<li>Primary-back：也称作passive replication/primary-based protocols。大概意思是副本间有主从关系，写数据时只写主副本，主副本负责应用修改到其他副本（可能同步也可能异步）。</li>
<li>State-machine：也称作active replication，所有的写操作暂存到某个独立的数据源，副本之间没有主从关系，分别去应用修改。</li>
</ul>
<p>借用一个图：<br><img src="/2017/06/03/distributed-mess/1.jpg" alt=""><br>注意上图虽然是用log在副本间执行写操作的，但其实也可以选择其他方式。</p>
<p>从实际情况来看，大多数系统都是primary-back，毕竟这种实现相对简单。这会引入几个问题：1. 如何确定主副本？2. 如何发现主副本异常？3. 主副本异常如何切换？这其实就是一个系统的HA（High Availability）方案，没啥标准答案，只能说八仙过海各显神通。</p>
<p>从副本修改的时机来看，replication又可以分为同步和异步的。<br>所谓同步，就是要更新所有副本之后，这个写操作才算成功，换句话说是强一致性的（所谓的Write-all-read-one，又称作ROWA）。一个例子就是hdfs pipeline，一个packet必须要写完所有副本并ack后才继续下一个。这可以算是一种“多写”的策略。可能是客户端去更新所有副本（State-machine），也可能是主副本去更新其他所有从副本（primary-back）。<br>异步基本都是基于WAL的最终一致性，比如上图中的log队列。在不使用共享存储的情况下，其实挺鸡肋的，这个东西在一致性要求不强的场景下作为读写分离还可以，但你要是把它当作HA方案，只能祝你好运了。。。比如mysql replication，就算主挂了，你敢直接让从服务器作为主么。。。</p>
<p>副本的分布策略也是个大问题。如果所有副本都在同一台机器上，哪还有啥意义？关键还是看你对数据可靠性的要求有多高。hdfs中没有跨机房的概念，分布策略一般是一个在本地，一个同机架，一个跨机架。其他系统也会有类似的策略，比如所谓的“两地三中心”。<br>题外话，为啥大多数系统是3副本？网上有人从各种角度论证3副本的优越性。。。我倒觉得这可能只是个先入为主的观念而已。如果是用了paxos/raft倒是还说得通，毕竟奇数个节点有利于表决。</p>
<p>另一个问题：副本的“粒度”。可能是机器级别的replication，比如mysql和redis；也可能是数据块级别的replication，比如hdfs中的block。有人称这两种情况为同构系统和异构系统。在大规模的分布式系统中，一般都是后者，数据恢复、新增副本都有很大优势。</p>
<p>相比手工实现replication机制，使用各种一致性协议更为靠谱，其中最著名的就是paxos了。Chubby的作者说这个世界上只有一种一致性算法，那就是paxos，其它的算法都是残次品。关于paxos的资料已经非常多了，不再赘述。这货以其难懂（很多时候我们都是自以为懂了）和难以实现而出名。。。还有各种变种。但在过去很长时间内它是唯一选择。整个算法的流程初看很简单，似乎按这个思路写个实现也很容易，但你很难证明它是“完备”的，在各种异常情况下都是正确的。<br>Zookeeper借鉴了paxos的思想，使用了一种称为<a href="https://cwiki.apache.org/confluence/display/ZOOKEEPER/Zab+vs.+Paxos" target="_blank" rel="external">Zab</a>的协议。原文：“The main conceptual difference between Zab and Paxos is that it is primarily designed for primary-backup systems, like Zookeeper, rather than for state machine replication.”。<br>在新出现的各种系统中（比如<a href="https://github.com/coreos/etcd" target="_blank" rel="external">etcd</a>），<a href="https://raft.github.io/" target="_blank" rel="external">Raft</a>是更主流的选择。它的功能和paxos大体等价，但更易于理解和实现。这里有一个非常赞的可视化教程：<a href="http://thesecretlivesofdata.com/raft/" target="_blank" rel="external">http://thesecretlivesofdata.com/raft/</a></p>
<p>这些一致性算法都是基于<a href="https://zh.wikipedia.org/wiki/Quorum_%28%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%29" target="_blank" rel="external">Quorum机制</a>实现的，通俗点说就是所有节点必须是已知的，一项决议要想通过就必须得到“大多数”（一般是过半）节点的同意。可以看出2PC也是类似的，只不过需要所有节点的同意。但Quorum只是一种“特例”，有些时候参与的节点是无法事先得知的，比如后面会说的区块链。这种场景下要达成一致，只能用其他办法。</p>
<p>另外，很多资料中会说paxos/raft是“强一致性”，个人觉得这是一种误解。paxos算法只需要过半节点的同意，一项决议就能通过。如果你每次都去读master，当然是强一致性，但这就失去分布式的意义了，副本纯粹作为backup；如果允许去读其他副本，那就有可能读到旧的数据，是最终一致性。相关分析见<a href="http://blog.kongfy.com/2016/08/%E8%A2%AB%E8%AF%AF%E7%94%A8%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7/" target="_blank" rel="external">这里</a>。</p>
<p>说到副本，不得不提Amazon Dynamo的NWR模型，这个很有意思。它将选择权交给了用户，把各种麻烦也交给了用户。。。N代表副本的数量，W代表一次写操作必须更新的副本数，R代表一次读操作必须读到的副本数。如果N=3/W=3/R=1，就是hdfs的模型了。如果W&lt;N，可能出现写冲突，必须要用户手动解决。</p>
<p>参考资料：</p>
<p><a href="https://zh.wikipedia.org/wiki/%E6%8B%9C%E5%8D%A0%E5%BA%AD%E5%B0%86%E5%86%9B%E9%97%AE%E9%A2%98" target="_blank" rel="external">拜占庭将军问题</a>：如何在多个节点故障的情况下达成一致，很好玩，还会引申出所谓的拜占庭错误<br><a href="https://www.zhihu.com/question/23167269" target="_blank" rel="external">https://www.zhihu.com/question/23167269</a><br><a href="https://www.zhihu.com/question/19787937" target="_blank" rel="external">https://www.zhihu.com/question/19787937</a><br><a href="https://en.wikipedia.org/wiki/Consistency_model" target="_blank" rel="external">https://en.wikipedia.org/wiki/Consistency_model</a><br><a href="http://web.eecs.umich.edu/~farnam/498/handout5b.pdf" target="_blank" rel="external">http://web.eecs.umich.edu/~farnam/498/handout5b.pdf</a>  ：详细介绍了各种consistency protocol<br><a href="https://arxiv.org/pdf/1103.2408.pdf" target="_blank" rel="external">Using Paxos to Build a Scalable, Consistent, and Highly Available Datastore</a><br><a href="https://segmentfault.com/a/1190000004474543" target="_blank" rel="external">https://segmentfault.com/a/1190000004474543</a><br><a href="http://coolshell.cn/articles/10910.html" target="_blank" rel="external">http://coolshell.cn/articles/10910.html</a>  ：注意这里有些观点是错误的<br><a href="http://hedengcheng.com/?p=970" target="_blank" rel="external">http://hedengcheng.com/?p=970</a>  ：登博的blog<br><a href="http://hedengcheng.com/?p=892" target="_blank" rel="external">http://hedengcheng.com/?p=892</a><br><a href="http://blog.csdn.net/followmyinclinations/article/details/52870418" target="_blank" rel="external">分布式事务与一致性算法Paxos &amp; raft &amp; zab</a><br><a href="http://www.infoq.com/cn/articles/nosql-dynamo" target="_blank" rel="external">解读NoSQL技术代表之作Dynamo</a></p>
<h2 id="sharding">sharding</h2>
<p>所谓sharding其实就是数据分片。现在分库分表中间件都应该是各个公司的标配了吧。。。这其实也不是什么新鲜概念，只不过是分布式环境下的分区表而已，分区策略也还是那些：基于range、基于hash、基于list。mq中也有类似的partition、routing key等概念。</p>
<p>最常用的还是基于hash的，比如redis中的slot（这和一致性hash中的虚节点很像）。但分布式系统中更常使用的是<a href="https://zh.wikipedia.org/wiki/%E4%B8%80%E8%87%B4%E5%93%88%E5%B8%8C" target="_blank" rel="external">一致性hash</a>，解决re-hash的问题，这也算是标配了。相关概念不再赘述。</p>
<p>sharding的方法很多，可能是手动的也可能是系统自动的。我曾见过直接在代码里计算分片，然后手动操作对应的库的，甚至读写分离也是通过代码手动做的；也有写在DAO层的；也有写在JDBC层的；还有用单独的query server去实现mysql jdbc driver的。反正是各显神通，不能一概而论。<br>不过呢，对用户友好的方式还是尽量让操作透明。用户应该纯粹把系统当作一个黑盒，不要去考虑内部的实现。</p>
<p>sharding的目的是为了扩展，最理想的情况下，系统应该能够线性扩展：整个系统的吞吐量和机器数成正比。加机器真是最简单的“优化”手段了。。。<br>但分片不是万能的，对于一些热点数据，还是可能集中在特定的几个分片（数据倾斜）。这个时候就要想其他办法，比如缓存，或者更换分片策略。</p>
<p>话说常有人纠结什么是水平切分，什么是垂直切分，我倒觉得不用太在意这些概念。关键还是看系统是怎么设计的，有些系统里根本没有那么多模式。<br>题外话：分片数为啥一般是2^n？同样的问题，为啥HashMap的Entry大小也是2^n？因为这样取模运算可以等价为按位与：<code>a % (2^n) = a &amp; (2^n - 1)</code>，提高运算效率。所以扩容时一般也是按2倍扩容。</p>
<p>参考资料：</p>
<p><a href="http://blog.codinglabs.org/articles/consistent-hashing.html" target="_blank" rel="external">http://blog.codinglabs.org/articles/consistent-hashing.html</a><br><a href="http://www.infoq.com/cn/articles/exploration-of-distributed-mysql-cluster-scheme" target="_blank" rel="external">http://www.infoq.com/cn/articles/exploration-of-distributed-mysql-cluster-scheme</a></p>
<h2 id="超时">超时</h2>
<p>只要是通过网络的调用，结果就可能有三种：成功、失败、超时，这个超时，就和数据库里的null一样，非常头痛。很多系统设计时会忘记处理超时的情况，然后在线上就会出现各种诡异的问题。。。比如HA方案中最常见的脑裂。。。</p>
<p>超时的问题在于，你不知道一个节点是真的挂了，还是只是网络出了问题。如果你认为它挂了，开始迁移它上面的数据，万一它等下又缓过来了呢，就可能会造成数据不一致的问题。</p>
<p><a href="https://en.wikipedia.org/wiki/Lease_%28computer_science%29" target="_blank" rel="external">租约机制</a>（lease）可以部分解决这个问题。其实这和OAuth中的token类似，是一个有时效性的“凭据”，最初是用于分布式cache。每个节点必须定时向Master更新自己的lease，如果lease失效，就停止对外服务。Master也可以确定节点彻底失联后才开始迁移数据。lease的可靠性在于它只依赖单次网络通信。租约也可以用来实现其他功能，比如hdfs用租约来实现单一写+多读（soft limit/hard limit机制），有点类似读写锁。<br>租约的问题在于它的有效期是基于时间的，所以各个节点之间时间误差不能太大。否则可能出现某个节点认为租约有效而其他节点认为无效的情况。</p>
<p>超时还可能导致一种“临界状态”：就是上面说过的，写操作在服务端完成了，但返回成功信息时超时了，客户端没有收到，这咋整？换言之需要客户端去处理超时的逻辑。这种时候没有啥固定的处理办法。。。还是要看系统怎么设计。可能是通过其他手段去验证是否成功（比如客户端重新读一次），也可能是直接重试。</p>
<h2 id="缓冲池">缓冲池</h2>
<p>缓冲（buffer）的目的就是为了协调cpu和磁盘的速度差距，在各种系统中也非常常见，甚至都算不上一种“机制”，而是自然而然的选择，比如InnoDB的buffer pool、MapReduce的io.sort.mb、hbase的Memstore。<br>其实这也不是啥新鲜概念，操作系统本身就大量使用了缓冲区，比如socket的发送/接收缓冲区。</p>
<p>buffer在不同系统中的作用可能完全不一样。以InnoDB为例，buffer pool可以说是整个系统的核心，负责对各种页的管理。这和操作系统的分页管理有点像了，会有各种page-in/page-out等逻辑，一般要搭配LRU/<a href="https://en.wikipedia.org/wiki/LIRS_caching_algorithm" target="_blank" rel="external">LIRS</a>算法；和netty中的内存池也有点像（chunk）。写数据时只会写内存，然后异步刷新脏页。如果不使用buffer而每次都直接写磁盘，性能会被磁盘拖累。但也可能导致一个问题，就是服务启动后需要预热。有些sql第一次执行很慢，第二次执行就会很快，就是这个原因。<br>hbase的Memstore，更是兼有随机写转化顺序写的功能（LSM Tree）。<br>至于MapReduce的io.sort.mb，就纯粹是排序用的一个临时空间而已。</p>
<p>另外，有人会纠结buffer和cache的区别。直观上来看，buffer是系统必要的一部分，buffer中的数据丢失会导致各种问题；而cache只是一种优化手段，有没有都无所谓，大不了回源重新读取。更详细的讨论见<a href="https://www.zhihu.com/question/26190832" target="_blank" rel="external">这里</a>，不过对这种事情的理解呢，千人千面。</p>
<p>题外话，“池化”也是非常常见的一个优化手段，线程池、连接池、内存池、对象池，都是一个道理，减少执行某个操作的开销。</p>
<h2 id="Master-Slave">Master-Slave</h2>
<p>为啥大多数分布式系统都是主从结构的？个人感觉，这是为了分散各种“状态”。</p>
<p><a href="/2016/09/06/zhen-second-hand-qianduan/#状态是万恶之源">以前</a>我就说过，状态是万恶之源。系统设计的关键就是明确有哪些状态、状态如何管理，各个节点之间共享的状态越少越好（share nothing），这对整个系统的可用性、扩展性都有巨大的好处。极端一点可以将所有状态都放到共享存储中（比如Zookeeper），然后把节点做成无状态的。<br>Master-Slave的本质就是一个“分工”，不同的节点分成不同的角色，维护不同的状态。比如hdfs，元数据由NameNode维护，block report由DataNode维护，DataNode不需要也不应该得知元数据相关的状态。这可能带来额外的问题：</p>
<ul>
<li>单点故障：Master不能是单点，需要使用各种HA方案</li>
<li>Master瓶颈：每次读写都要经过Master，它的qps限制了整个系统的上限，可以适当使用客户端缓存解决</li>
<li>容量限制：Master是否能存下所有的状态？如果不能，还要进一步拆分，比如hdfs federation</li>
</ul>
<p>如果是去中心化的系统，所有的节点是平等的，那么每个节点都必须得知其他所有节点的状态（一般是通过心跳），这可能导致巨大的网络开销，限制了系统的扩展性（加一台机器都可能导致带宽需求成倍增长）。而且很多时候并不需要全局的状态。<br>去中心化的一个例子就是redis集群。所有的redis节点都是平等的，需要向其他节点push自己的状态，但redis做了一些优化，每次心跳不会向所有节点push，而是随机挑选5个，再加上“久未联系”的节点（上次心跳至今超过某个阈值）。但这不是治本办法。我不知道这套机制在实际的生产环境中运行的如何，但感觉肯定有坑。。。<br>除了Master-Slave，也有所谓的Master-Master结构，不过感觉很少见。这种系统的问题就是如何解决写冲突，一般都只能让用户手动解决，就像git/svn一样。</p>
<p>其实最关键的原因是：Master-Slave结构工程上实现起来简单。。。<br>不过呢，从一个强迫症的角度来说，明显去中心化的网络才是“更美”的，比如区块链，比如P2P，比如DHT，比如那啥主义。。。</p>
<h2 id="HA">HA</h2>
<p><a href="https://en.wikipedia.org/wiki/High_availability" target="_blank" rel="external">HA</a>是对分布式系统的基本要求，有时也称作fault tolerance。</p>
<p>关键的几个问题（其实上面已经说过了）：</p>
<ul>
<li>如何在节点间同步状态（Active和Standby）</li>
<li>如何发现节点挂掉</li>
<li>如何切换节点</li>
</ul>
<p>没什么标准方法，不同系统的实现可能完全不一样。</p>
<p>状态的同步，参考上面replication章节。<br>发现节点挂掉，一般通过心跳+超时机制+lease。有了zookeeper后，也可以简单的利用zk的ephemeral node，但这种情况下zk的session超时时间就要谨慎设置，否则很容易踩坑。hdfs的NameNode HA比较奇葩，通过定时的rpc调用去发现节点挂掉（zkfc）。关于hadoop HA机制的更多细节见我<a href="/2015/04/01/ha-and-zookeeper/">以前的文章</a>。此外也有用keepalived+VIP的，这种方案在nginx中似乎很常见。<br>至于节点的切换，服务端一般是通过心跳，客户端可能是通过类似配置中心（比如zk的通知机制）或者直接重试（比如hdfs的客户端）。</p>
<h2 id="其他">其他</h2>
<p>一些零散的想法：</p>
<ul>
<li>即使是非常小概率的异常，集群规模一大，也可以称作是必现的，所以设计时不能忽略任何可能的异常情况</li>
<li>仔细考虑服务降级，要考虑好哪些是强依赖，哪些是弱依赖，要认为所有外部依赖都是不可靠的，包括网络。我见过一些应用，redis挂了，就不可用了。。。有时候防御式编程也是不得已的。这方面Netflix的<a href="http://www.infoq.com/cn/news/2013/02/netflix-opensource" target="_blank" rel="external">猴子</a>非常出名。</li>
<li>没有银弹，必须要trade-off，不存在one fits all的系统</li>
<li>不管什么架构，首先出问题的一般都是IO，无论是网络IO还是磁盘IO</li>
<li>不管什么架构，不管你怎么设计，似乎总能找出一些极端情况，击溃“不丢数据”的传说。hadoop的HA设计的那么美好，实际跑起来不也是一堆坑。。。当年我可是没少半夜起来处理故障，又想起了被hadoop支配的恐惧。。。</li>
<li>太理论的东西了解下就行，不要过于执念</li>
<li>跟无锁编程类似，事务中也尽量用CopyOnWrite、MVCC等方法优化性能</li>
<li>其他常用手段：列式存储+数据压缩</li>
<li>其他常用数据结构：Bloomfilter、LSM Tree（hbase/LevelDB）、Timing Wheel</li>
</ul>
<h1 id="分布式事务">分布式事务</h1>
<p>这一直是个老大难问题。因为没有什么标准的解法，很多时候要看自己的需求。</p>
<p>之前有人总结过涉及分布式事务的各种场景，借用下：</p>
<ul>
<li>分库导致的：或者说，同一个系统中的事务，比如跨多个mysql库，比如Spanner中跨多个paxos group</li>
<li>跨多数据库：比如跨mysql和oracle</li>
<li>跨数据库和其他组件：跨各种中间件的调用，典型的是数据库+mq（事务消息）</li>
<li>微服务架构下，跨多个服务的调用：实际中这种情况更常见，尤其是现在“X-as-a-Service”理念盛行的情况下</li>
</ul>
<p>对每种场景，都有完全不一样的做法。甚至在不同的场景下，“事务”的含义都不太一样。</p>
<p>其实RDBMS中早有考虑过分布式事务（<a href="https://en.wikipedia.org/wiki/X/Open_XA" target="_blank" rel="external">XA事务</a>，这个名字很奇怪），<a href="https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4" target="_blank" rel="external">两阶段提交</a>（2PC）也是源自XA的。简单点说就是所有参与节点先做本地事务（prepare），然后决定是commit还是回滚。这可以说是分布式事务的一个参考标准，大多数资料里都会讲2PC，但实践中用的貌似不是很多。2PC的问题在于协调者是单点，而不能很好的处理超时和各种failover，可能阻塞，各个角色之间需要的交互次数很多，效率相对较差。虽然有后续的<a href="https://zh.wikipedia.org/wiki/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4" target="_blank" rel="external">3PC</a>改进了部分问题，但也是不治本。<br>但2PC更重要的是一种“思路”，实现的时候可以想办法去优化，比如Spanner中就大量使用了2PC。</p>
<p>注意不要把2PC和之前的副本一致性算法（paxos/raft之类）相混淆，他们要解决的问题不同，paxos一般用于保证同一数据块多个副本之间的一致，而2PC用于保证在不同数据块上的操作的原子性。在实践中我们谈到“事务”时，大多数时候也是更关注原子性。</p>
<p>其实2PC不只用于分布式环境下，很多场景下都可以。比如mysql中利用XA事务实现存储引擎和binlog的一致，称作内部XA事务。总之只要需要原子性的地方，都可以考虑2PC。</p>
<p>说到分布式事务，就不得不提到ebay的<a href="http://queue.acm.org/detail.cfm?id=1394128" target="_blank" rel="external">BASE</a>架构（虽然我觉得这个BASE是为了对应ACID强行凑出来的词。。。），中文版见<a href="http://blog.csdn.net/tenfyguo/article/details/8087465" target="_blank" rel="external">这里</a>。虽然这个文章很古老了，但仍有借鉴意义。它的核心价值在于提出了利用消息实现的最终一致性，一般被称作消息事务，也有点像WAL。具体的操作逻辑原文讲的很清楚，不再赘述。<br>ebay是使用本地事务来保证“业务操作” + “发送消息”的原子性的，在实际中，一般是直接用mq，但如何保证原子性就要自己考虑了。</p>
<p>BASE架构的问题在于：1. 如果消费者处理消息失败怎么办？可以引入适当的重试机制；2. 但是如果想回滚之前已经提交的本地事务，就必须要人为介入了；3. 消息的处理如果不是幂等的，消费时需要严格的exactly-once语义（大多数mq都只能保证at-least-once），这限制了很多场景；4. 消息的顺序如何处理，有些场景要求消息必须是有序的。<br>总之就是：能解决问题，但是否适用还是要看自己的需求。</p>
<p>其实仔细想想，分布式事务的关键在哪里？如果说事务的本质就是并发，解决并发问题一般有两个手段：锁和CAS，那2PC就是基于锁的实现，而锁是基于悲观策略的。这是一个兜底的方案，它保证结果一定是正确的，但一般都比较低效。更常见的做法是：所有节点直接做本地事务，认为发生冲突的概率很小，一个事务很可能成功（乐观策略），如果有任意本地事务提交失败，则触发全局的回滚。这一般被称作事务补偿，也是一种最终一致性实现（可能被用户看到中间状态）。补偿策略的关键：1. 如何确定一个本地事务是否成功？2. 如何回滚？这两个问题如果是基于DB的还好说，但广义的分布式事务未必是跟数据库相关的。</p>
<p>这方面比较出名的是<a href="http://www.bytesoft.org/2017/03/29/tcc/" target="_blank" rel="external">TCC模式</a>，这个模式从何而来已不可考，似乎是从阿里传开来的（我能吐槽下中间件的命名方式么：TCC/TXC/XTS/GTS傻傻分不清楚）。TCC模式可以认为是2PC的威力加强版，try阶段直接完成本地事务提交（减少阻塞），后续看情况决定调用confirm还是cancel。这种方式相当于强制程序员在每个事务之前事先写好回滚逻辑（还要保证confirm和cancel是幂等的），然后交给系统去执行。如果每次调用都按这个模式来，毫无疑问会增加很多工作量，不同业务逻辑的回滚代码是不可复用的。</p>
<p>更智能的方式是系统自动决定如何回滚。要做到这点，调用链路上的所有中间件都要进行改造，不管是数据库、MQ、还是RPC。每个服务也都要事先确定好自己的回滚逻辑：订单服务失败如何回滚、支付服务失败如何回滚，细化到每个接口的程度。换句话说，每个系统都要事先确定自己的补偿逻辑。<br>鄙厂的TCC实现中，只打通了DB层，是通过在各个业务库新增一张log表来实现的，记录本地事务和对应的分布式事务的执行情况。回滚时可以调用手动指定的sql，也可以指定某个方法。在整个调用链路中（dubbo），加了事务注解的方法都会自动加入到分布式事务中来，还挺好用的。<br>其他厂应该也有类似的中间件，原理都差不多。</p>
<p>本地事务失败时，也可以加上一定的重试机制。毕竟全局回滚的代价还是挺大的。</p>
<p>但TCC和2PC都有一个问题没解决：需要一个全局的协调者。这似乎是没法避免的，只能尽量提高协调者的可用性。万一协调者挂了就悲剧了。<br>有一种做法是本地事务失败时，失败的那个参与者发个消息，然后其他参与者看情况决定是否回滚。这样就不用协调者去触发回滚逻辑了，在一定程度上可以解决协调者的问题。但这要求所有系统增加处理失败消息的逻辑，相当于把处理逻辑分散了。</p>
<p>至于MVCC，一般只能用在上述四种场景中的第一种，即只能用来在一个独立的系统中实现分布式事务。毕竟跨多个异构系统的话，版本号如何确定？强行用MVCC的话反而麻烦。</p>
<p>最后，我们还有一个终极大招：人工处理。。。</p>
<p>题外话：我本来以为TCC是2PC的另一种表述，try=prepare，confirm=commit，cancel=rollback，其他资料中也确实有人持这种观点，并不认为TCC是一种补偿机制。不过这样还有啥意义呢。。。根本原因在于TCC不是一个严格定义的名词，不要太纠结。</p>
<p>参考资料：</p>
<p><a href="http://weibo.com/ttarticle/p/show?id=2309614012527083212086" target="_blank" rel="external">http://weibo.com/ttarticle/p/show?id=2309614012527083212086</a><br><a href="http://weibo.com/ttarticle/p/show?id=2309403965965003062676" target="_blank" rel="external">http://weibo.com/ttarticle/p/show?id=2309403965965003062676</a><br><a href="https://www.zhihu.com/question/21612832" target="_blank" rel="external">https://www.zhihu.com/question/21612832</a><br><a href="http://blog.jobbole.com/108569/" target="_blank" rel="external">http://blog.jobbole.com/108569/</a><br><a href="https://zhuanlan.zhihu.com/p/25933039" target="_blank" rel="external">https://zhuanlan.zhihu.com/p/25933039</a><br><a href="http://jm.taobao.org/2016/11/24/2016-1111-GTS/" target="_blank" rel="external">http://jm.taobao.org/2016/11/24/2016-1111-GTS/</a><br><a href="https://gist.github.com/weidagang/1b001d0e55c4eff15ad34cc469fafb84" target="_blank" rel="external">https://gist.github.com/weidagang/1b001d0e55c4eff15ad34cc469fafb84</a> ：主要看注释</p>
<h1 id="Megastore_&amp;_Spanner">Megastore &amp; Spanner</h1>
<p>这两个东西放在一起说说，只是总结一些个人感想。google的论文缺少了很多细节，很多地方只能靠脑补。</p>
<p>其实GFS-&gt;BigTable-&gt;Megastore-&gt;Spanner是有迹可循的：BigTable基于GFS实现了强一致性、索引、semi-relational等特性，Megastore则在BigTable上实现了更友好的API、paxos复制、多行事务等，最后的Spanner则是集大成者。</p>
<p>Megastore：<br><img src="/2017/06/03/distributed-mess/2.png" alt=""></p>
<p>熟悉BigTable/hbase的话，就会觉得Megastore很精妙：</p>
<ul>
<li>Megastore的大部分功能都只是“客户端库”，最后会映射为底层的BigTable操作。自身几乎没增加什么额外的状态，复制服务器、协调者、witness等角色也只是辅助性的。</li>
<li>利用BigTable单行操作的原子性，保证同一个entity group中多行事务的强一致性。基本原理就是将redo日志保存在单独一行内，写redo成功就算事务提交。那如果整个系统只有一个entity，是不是就可以说BigTable也支持多行事务了？似乎没毛病。。。但这样估计日志量要爆炸。。。entity group模型从某种意义上来说也是为了日志的sharding。</li>
<li>由于使用了redo，所以副本的数据未必是最新的，读数据时可能需要“回放”（catch up）</li>
<li>跨group只能实现基于消息的最终一致性或基于2PC的强一致性</li>
<li>基于paxos的数据同步机制，感觉是第一次出现在这么大规模系统中。相比传统的Master-Slave同步，paxos的好处就是可以故障自动切换。而且加了很多优化，local read之类的。</li>
<li>不过我有一点疑问：BigTable不是将数据副本委托给底层的GFS？这个paxos的“副本”到底是啥。也可能是hbase让我先入为主。有资料说是用于在不同集群之间同步redo的，如果是跨多个BigTable集群部署，确实可能。</li>
<li>由于BigTable中的数据自带版本号，所以Megastore天生就支持MVCC的并发控制。个人猜测：每个客户端写数据时，redolog先写本地buffer，提交前检查版本号和事务开始时是否相同，不同则回滚。相同则<a href="http://dl.acm.org/citation.cfm?doid=1132863.1132867" target="_blank" rel="external">使用paxos协议提交</a>日志。</li>
<li>“使用paxos协议提交日志”，这说起来简单，但实现起来肯定巨复杂。如果竞争一个分布式锁然后提交，是不是也是同样效果？分布式锁一般也都是基于paxos实现的。</li>
<li>读和写都可以从任意副本开始（原文： Any node can initiate reads and writes），这个很方便。读数据时，协调者用于决定读哪个副本。但每次写都必须同步到所有副本（强一致性），难道不会慢么。</li>
<li>Megastore支持local/global两种索引，但也是没有跳出BigTable的限制，还是在rowkey上做文章。我很好奇是怎么在数据和索引之间保持一致的，local索引还还说，只是同一个entity group内的一些索引行；global索引则是单独的一个索引表。如何实现跨表更新的原子性，论文里似乎也没说，估计也是2PC吧。之前常见的二级索引方案，大多都是离线更新索引的。</li>
</ul>
<p>个人感觉，Megastore最大的意义在于揭示了BigTable是还一个非常底层的抽象，在它上面还可以有各种各样的应用层，比如<a href="https://phoenix.apache.org/" target="_blank" rel="external">Phoenix</a>之类，正如hive之于MapReduce。</p>
<p>Spanner：<br><img src="/2017/06/03/distributed-mess/3.png" alt=""><br><img src="/2017/06/03/distributed-mess/4.png" alt=""></p>
<p>Spanner刚出来的时候，大家都惊为天人，毕竟全球规模的扩展性+关系模型+强ACID前所未见，很多人觉得这已经突破CAP了（google还有专门的<a href="https://research.google.com/pubs/pub45855.html" target="_blank" rel="external">一篇论文</a>说这个，作者就是提出CAP理论的那哥们，<a href="http://dockone.io/article/2129" target="_blank" rel="external">中文版</a>）。Spanner中的很多机制都继承自Megastore，比如paxos复制、关系模型等，但在此基础上做了很多创新。GPS+原子钟实现的TrueTime API，脑洞真是大。。。关键人家还有能力把脑洞变成现实。。。</p>
<ul>
<li>原文：At the highest level of abstraction, it is a database that shards data across many sets of Paxos state machines in datacenters spread all over the world. </li>
<li>每个zone可以认为就是一个datacenter，部署了一套BigTable集群。在同一个datacenter里，zone也可以用来做数据隔离。</li>
<li>Spanner的数据模型类似BigTable，但又不太一样。似乎是越过了BigTable层，直接操作数据，少了一层API调用。tablet的概念和BigTable类似，但每个tablet中的行未必是连续的。directory的概念和Megastore中的entity基本一致，多个directory可以同属于一个tablet。每个tablet的所有副本组成一个paxos group。</li>
<li>论文中说底层的存储模型是<code>(Key: string, timestamp: int64) -&gt; string</code>，还强调和BigTable不同，我咋没看出来。。。</li>
<li>如何将数据对应到tablet，元数据的结构和管理，如何回收不需要的版本，这些论文里都没说。。。只能靠猜</li>
<li>和Megastore一样，写也是强一致性</li>
<li>Spanner中的事务是为了所谓的long-lived tranction设计，所以它没有采用类似Megastore中的乐观机制（多个事务如果同时提交到一个paxos group，只有一个事务能提交成功，另一个事务会重试，即使这两个事务实际上没有冲突），而是用了传统的两阶段锁（所以按SQL标准而言隔离级别是可序列化？），每个paxos group中都会有一个lock table（用于记录每一行是否被锁定）和transaction manager（简称TM，用于2PC）<ul>
<li>论文中详细讲了跨group时的2PC过程，却没有讲单个group中的事务是如何处理的，估计是当作2PC的一种特例处理</li>
<li>论文中一再提到“外部一致性（external consistency）”，我却找不到这个词的准确定义。个人感觉就是一个写提交之后，修改对后续所有的读可见。<br>（这里的“之后”、“后续”都是很模糊的词，更严格的表述应该用TrueTime）</li>
<li>paxos无处不在，不只数据会被复制，锁的状态、TM的状态都会被复制</li>
<li>只有读写事务才需要锁，只读事务（估计要API中显式声明）/快照读是不需要锁的，但也是可能阻塞的</li>
<li>只读事务，其实就是系统自动分配时间戳的一个快照读</li>
<li>Spanner认为过度使用事务造成的性能下降的恶果，应该由应用的开发者承担，而不能因为性能问题，就不提供事务支持。</li>
<li>多个paxos group参与的2PC，这工程难度，想想就蛋疼。。。</li>
</ul>
</li>
<li>TrueTime API主要是一个<code>TT.now()</code>调用：返回一个时间区间<code>[earliest, latest]</code>，保证触发这个调用的“真实时间”必定在这个区间之内<ul>
<li>使用时间戳来做concurrent control不是新鲜概念，但大多数都没有处理好时间的误差。Spanner是第一个能在全球范围内做到这点的（原文：Spanner is the first system to provide such guarantees at global scale.）</li>
<li>时间戳会用作数据的版本号。原文：The timestamps reflect serialization order</li>
<li>Spanner保证同一个group内，事务的时间戳都是单调递增的，即使是切换了leader。个人感觉，这就像一个全局的事务id生成器，但是效率和精度都好很多</li>
<li>写数据（Read-Write Transaction）：注意使用两阶段锁时，读也要加锁。先写客户端buffer，所有写完成之后，发起2PC。这里就很有意思了：coordinator leader会为整个写操作选择一个时间戳s作为事务id（也可以认为是数据的版本），这个时间戳必须大于所有participant leader提交prepare record的时间，还必须大于<code>TT.now().latest</code>，然后coordinator leader会写一条commit record，但不会立即通知participant也commit<br>（2PC的第二阶段），而是要等到<code>TT.after(s)</code>为true，换句话说必须保证s已经是“过去时”，然后才会通知所有participant也commit。这时数据才会对外可见。</li>
<li>为啥写操作要这样设计？猜测是为了保证外部一致性，防止因时间误差导致一个读操作读取到不该读的值。</li>
<li>读数据（Read-Only Transaction）时：首先必须给读事务分配一个时间戳s。最简单的就是取<code>TT.now().latest</code>，但如果读到了一些比较落后的副本，可能必须等副本的数据追上来（细节论文没说）。Spanner对此做了一些优化，如果读的是单个group，并且这个group内没有未提交的事务，可以优化为s=最后一个已提交事务的时间戳。</li>
<li>看过读写流程后，发现一个问题：如果分配给一个写操作的时间戳是s，但这个写操作真实提交的时刻是在s之后，关键这个“之后”是多久？除了2倍的TrueTime误差，paxos提交也要一些时间的。如果假设这个写操作在s+10时刻才提交成功，那我在s+1时刻的一个读操作，是否能读到s的数据？我纠结了挺长时间，一度怀疑Spanner是unrepeatable-read。。。后来又仔细看了下论文，发现有解释，在这种情况下Spanner会认为副本not sufficiently up-to-date，会阻塞读操作直到事务结束的。。。但又是没有细节。总之，外部一致性不容打破。</li>
<li>“未来读”（指定了一个将来的时间戳的快照读）应该是没什么意义的。。。</li>
<li>之所以想到“未来读”，是因为论文中还提到一种事务“Schema-Change Transaction”，可以做在线DDL，要指定一个未来的时间戳t，不知道这个t的作用是啥，随意指定的还是有啥算法？</li>
<li>猜测Schema-Change Transaction也只是更新元数据，不会动实际的数据</li>
</ul>
</li>
<li>Spanner的论文中多次吐槽了Megastore（原文：its relatively poor write throughput）。。。</li>
<li>感觉Spanner中的读和Megastore的current read好像没啥差别。。。除了TrueTime实现的更精准的版本号</li>
<li>不知道Spanner支持二级索引不。。。论文里一句都没提，不过Megastore都支持了。</li>
</ul>
<p>Spanner的事务处理似乎是借鉴了<a href="https://research.google.com/pubs/pub36726.html" target="_blank" rel="external">Percolator</a>的，后者使用一个oracle来生成全局递增的时间戳。Spanner的开源实现中，也有类似的做法。关于Percolator事务机制可以参考<a href="https://pingcap.com/blog-percolator-and-txn-zh" target="_blank" rel="external">这个文章</a>，原理和Megastore类似，也是利用BigTable单行的原子性。但redo不是写在专门的某行（Megastore中的entity行），而是在写入的行里挑选（不知是不是随机）一个作为primary，其他行保留一个指向primary的类似指针的东西。这种模式如果写入频繁估计会大量冲突的，估计也会被Spanner吐槽“poor write throughput”。。。<br>话说大家都喜欢拿BigTable的单行事务做文章。。。</p>
<p>最近关于Spanner的新闻就是Cloud Spanner了。Cloud Spanner只支持SQL查询，写操作是自定义的API，那估计原版的Spanner也是这样。<br>另外google又发了一篇Spanner的论文（<a href="http://sigmod2017.org/sigmod-program/" target="_blank" rel="external">sigmod2017</a>）：Spanner: Becoming a SQL System。从名字来看似乎是一些user interface上的改进？</p>
<p>题外话：以前的google总是发表一篇论文证明自己的NB，然后退到一边静静观察；最近却越来越喜欢直接参与开源项目，热衷于制订行业标准了。比如<a href="https://github.com/kubernetes/kubernetes" target="_blank" rel="external">Kubernetes</a>、<a href="https://beam.apache.org/" target="_blank" rel="external">Apache Beam</a>、<a href="https://www.tensorflow.org/" target="_blank" rel="external">TensorFlow</a>。</p>
<p>参考资料：</p>
<p><a href="https://research.google.com/pubs/pub36971.html" target="_blank" rel="external">https://research.google.com/pubs/pub36971.html</a><br><a href="https://research.google.com/archive/spanner.html" target="_blank" rel="external">https://research.google.com/archive/spanner.html</a><br><a href="http://blog.csdn.net/techq/article/details/7039208" target="_blank" rel="external">http://blog.csdn.net/techq/article/details/7039208</a><br><a href="http://blog.nosqlfan.com/html/1195.html" target="_blank" rel="external">http://blog.nosqlfan.com/html/1195.html</a><br><a href="http://www.yankay.com/google-spanner%E5%8E%9F%E7%90%86-%E5%85%A8%E7%90%83%E7%BA%A7%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E6%95%B0%E6%8D%AE%E5%BA%93/" target="_blank" rel="external">Google Spanner原理 - 全球级的分布式数据库</a><br><a href="https://www.zhihu.com/question/61245513" target="_blank" rel="external">https://www.zhihu.com/question/61245513</a><br><a href="http://www.infoq.com/cn/news/2017/02/Google-Cloud-Spanner-hit-CAP" target="_blank" rel="external">http://www.infoq.com/cn/news/2017/02/Google-Cloud-Spanner-hit-CAP</a></p>
<h1 id="TiDB">TiDB</h1>
<p>其实Spanner的开源模仿者很多，谁能做好了就是下一个hadoop。模仿的最大难题就是如何实现TrueTime，但很多应用达不到google的规模，所以可以使用一些替代手段。</p>
<p>目前提到比较多的就是<a href="https://github.com/cockroachdb/cockroach" target="_blank" rel="external">CockroachDB</a>和TiDB了。关于TiDB，PingCAP老大<a href="http://chuansong.me/n/720316151966" target="_blank" rel="external">这篇文章</a>讲的很好，逻辑非常清晰。里面一句话非常赞同：“Infrastructure领域闭源的东西是没有任何生存机会的”。<br>话说前段时间朋友圈被PingCAP成功融资的消息刷屏了。。。</p>
<p><img src="/2017/06/03/distributed-mess/5.png" alt=""></p>
<ul>
<li>分层架构十分清晰，选择直接支持mysql driver真是太明智了</li>
<li>TiDB底层存储基于<a href="https://github.com/facebook/rocksdb" target="_blank" rel="external">RocksDB</a>，是facebook基于LevelDB修改而得</li>
<li>一行的数据都是存在一个KV Pair中，不会被切分，这点和BigTable很不一样。这会限制一些超“宽”的表的使用。</li>
<li>Raft实现来自etcd，将原来的go实现用Rust重写，但TiDB的其他部分都是go。。。选择Rust似乎是<a href="https://zhuanlan.zhihu.com/p/27335235" target="_blank" rel="external">因为GC</a>。</li>
<li>TiDB事务模型也是基于Percolator，依赖于一个单点的服务TSO生成单调递增的事务id。但做了一些修改，不会直接在原始行中记录锁信息，而是一个单独的meta行。</li>
<li>TiDB补充了Spanner中非常多的细节，比如元数据、索引。基本思路是还存在底层的KV中，但设计不同的prefix存不同的数据。</li>
<li>基于KV实现的SQL引擎已经有很多研究了。最关键的是如何生成分布式的执行计划，还有如何处理join，如何做谓词下推之类的。具体的我就不是很懂了。</li>
</ul>
<p>参考资料：</p>
<p><a href="https://pingcap.com/bloglist-zh" target="_blank" rel="external">PingCAP官网</a>有很多第一手的资料<br><a href="https://zhuanlan.zhihu.com/newsql" target="_blank" rel="external">知乎专栏：TiDB 的后花园</a></p>
<h1 id="区块链">区块链</h1>
<p>为啥会想到区块链（Blockchain）呢，因为这也是一个所谓的“分布式共识”问题，这其实也是分布式领域的一个经典问题了。核心在于如何保证某个变更在网络中是一致的，是被大家都承认的，同时这个信息是被确定的，不可推翻的。</p>
<p>一些名词：</p>
<ul>
<li>交易（Transaction）：一次操作，导致状态的一次改变，如添加一条记录；</li>
<li>区块（Block）：记录一段时间内发生的交易和状态结果，是对当前状态的一次共识；</li>
<li>链（Chain）：由一个个区块按照发生顺序串联而成，是整个状态变化的日志记录。</li>
</ul>
<p>通俗点说，区块链就像是一个全局唯一的链表，网络中的每个节点都想向链表里添加数据，但最终只有一个节点能成功。这就和paxos/raft关注的问题有点像了。不过paxos关注的是“尽快”达成共识，每个acceptor都会优先支持提案号更大的proposal。而区块链的做法似乎又有不同。</p>
<p>paxos/raft达成共识都是基于所谓的Quorum机制，即一次提议必须经过大多数节点的认可才算通过。而区块链中的节点更多，网络情况会更复杂，也很可能有网络分区，不能再用类似的办法了。每个节点在提出proposal（姑且称之为proposal，声称自己算出了某个区块，即挖矿）后，会向其他节点广播，不管他们是否确认，自己都会基于这个区块继续计算。其他节点可能会认可这个proposal，也基于此继续计算；也可能他们本地的链更长（每个节点只认最长的链），而忽略这个proposal。所以区块链中的“共识”是有可能被撤销的。。。也就是所谓的“分叉”。而paxos中所有节点一旦达成了共识，结果就不会改变。</p>
<p>这其实是一种很讨厌的“不确定”的状态，一个节点不知道自己算出的区块到底会不会被整个系统认可。以比特币为例，A转了一笔钱给B，但B要过一段时间才能使用这笔钱（需要至少经过6个区块的确认，大概一个小时，因为被撤销的概率很小了）。但似乎从数学上可以证明，整个系统最终会达成一个一致的状态。</p>
<p>为了减少这种分叉的情况，一个方法就是提高proposal的门槛。如果一个系统里每个人都能随意提议，要达成共识就很难。具体到比特币而言，就是Proof of Work机制：每一个节点如果想提出proposal，必须进行大量的数学计算（挖矿），算力越高提议的可能性就越大。所谓的计算其实和暴力破解没啥区别：先生成一个随机数，然后计算h = hash(上一个块的hash + 这段时间的交易记录 + 随机数)，如果h小于某个特定值（系统会动态调整这个值以保证大概每10分钟挖出一个区块，所以系统的总算力越大，挖矿就越难）就认为计算成功，这个区块是合法的，可以向其他节点广播了。否则就只能换一个随机数然后重试。挖矿成功后会得到系统奖励的比特币，也可以得到区块中所有交易的手续费。所以说，比特币的价值是通过计算力背书，而不是像传统的纸币是政府信用背书。</p>
<p>但我还是有些想不通：</p>
<ul>
<li>如果整个系统不发生交易，是不是就不会有新区块了？换言之，一个区块中可以不包含任意交易记录么？</li>
<li>如果发生了网络隔离，比如30%的节点失联10个小时（之前不是就有过挖断海底光缆的情况），这30%的节点会在自己的“小圈子”里计算出很长的一个链。但网络隔离恢复后，这个链上所有新增的区块都要被抛弃？所有交易也都作废？那别人转给我的钱到底啥时候可用。。。这是“51%攻击”的一种特例么？</li>
</ul>
<p>去中心化的系统，相比Master-Slave，实现/分析一般都更复杂，会觉得有很多“不确定性”，但也更有意思。设计的好的话，这种系统一般都会更健壮。</p>
<p>题外话：其实我只是个一边看着显卡涨价，一边看着游戏玩家们哭天抢地的围观群众。。。</p>
<p>参考资料：</p>
<p><a href="https://bitcoin.org/bitcoin.pdf" target="_blank" rel="external">比特币白皮书</a><br><a href="https://www.gitbook.com/book/yeasy/blockchain_guide" target="_blank" rel="external">区块链技术指南</a><br><a href="https://yq.aliyun.com/articles/65264" target="_blank" rel="external">https://yq.aliyun.com/articles/65264</a><br><a href="https://www.zhihu.com/question/27687960" target="_blank" rel="external">https://www.zhihu.com/question/27687960</a><br><a href="https://www.zhihu.com/question/37290469" target="_blank" rel="external">https://www.zhihu.com/question/37290469</a></p>
<h1 id="其他-1">其他</h1>
<p>杂七杂八的其他资料：</p>
<p><a href="http://www.infoq.com/cn/articles/raft-paper" target="_blank" rel="external">Raft论文译文</a><br><a href="http://kabike.iteye.com/blog/2182036" target="_blank" rel="external">http://kabike.iteye.com/blog/2182036</a><br><a href="http://amturing.acm.org/p558-lamport.pdf" target="_blank" rel="external">Time, Clocks, and the Ordering of Events in a Distributed System</a><br><a href="https://docs.oracle.com/cd/E23824_01/html/821-1455/a00intro-21293.html" target="_blank" rel="external">https://docs.oracle.com/cd/E23824_01/html/821-1455/a00intro-21293.html</a><br><a href="http://www.cnblogs.com/mmjx/archive/2011/12/19/2290540.html" target="_blank" rel="external">http://www.cnblogs.com/mmjx/archive/2011/12/19/2290540.html</a><br><a href="http://duanple.blog.163.com/blog/static/709717672011330101333271/" target="_blank" rel="external">http://duanple.blog.163.com/blog/static/709717672011330101333271/</a></p>
<p>话说，之前的评论系统挂了。。。换成了disqus，以前的评论都丢了，虽然也没多少。。。<br>我的react后台项目停滞挺久了，真是抱歉。。。最近确实各种事情比较头痛。。。以后尽量多分一些精力吧。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>这算是老本行？终于暂时爬出了前端的大坑。。。</p>
]]>
    
    </summary>
    
      <category term="分布式" scheme="http://jxy.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Realtime Web]]></title>
    <link href="http://jxy.me/2017/05/10/realtime-web/"/>
    <id>http://jxy.me/2017/05/10/realtime-web/</id>
    <published>2017-05-10T03:42:27.000Z</published>
    <updated>2017-06-30T13:08:35.000Z</updated>
    <content type="html"><![CDATA[<p>前段时间研究了下WebSocket，趁着还没忘总结下。</p>
<a id="more"></a>
<p>其实除了WebSocket还研究了其他一些东西，顺便还写了个调试WebSocket的<a href="https://github.com/jiangxy/websocket-debug-tool" target="_blank" rel="external">小工具</a>，自己会写前端就是好。。。</p>
<h1 id="先考个古">先考个古</h1>
<p>一直以来，前端都缺少一种实时的跟后端交互的机制。这其实是http协议的限制。http协议是典型的“请求-响应”式的协议，换句话说后端不能主动向前端“推送”数据，而必须等待前端先发起请求。</p>
<p>为了实现数据的实时更新，人们想了很多办法：</p>
<ul>
<li>短轮询：最简单的办法。既然必须要前端去获取数据更新，又要实时，干脆就不断的去服务端查询，比如每隔1秒就发起一次ajax看是否有新数据。典型的quick and dirty，会有很多无用的请求，对服务端压力也会比较大。</li>
<li>长轮询：还是轮询，但处理每次请求时，如果没有新数据，服务端不会立刻返回，而是会先hold一会，直到有数据更新或者超时。前端在一次请求结束后立即发起下一次请求，而不是像短轮询一样定时发起请求。好处显而易见，对后端的请求少了很多，但其实没有解决根本问题。这里有个问题就是后端要hold一个请求多久？有<a href="https://tools.ietf.org/html/rfc6202" target="_blank" rel="external">文献</a>提到过最好是30秒~120秒，这个要看实际情况，受网络环境影响都很大，比如防火墙。</li>
<li>HTTP Streaming：原理其实是<a href="https://zh.wikipedia.org/wiki/%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81" target="_blank" rel="external">HTTP Chunking</a>，中文译作“分块传输编码”。当http响应中<code>Transfer-Encoding</code>的值等于<code>chunked</code>时，服务端不必一次性返回所有数据，而是可以分批次的发送。利用这一特性，可以实现后端向前端的实时的数据推送。在此基础上，又衍生出了<a href="https://en.wikipedia.org/wiki/Comet_%28programming%29" target="_blank" rel="external">Comet</a>和<a href="https://developer.mozilla.org/zh-CN/docs/Server-sent_events/Using_server-sent_events" target="_blank" rel="external">Server-sent Event</a>（一般简称SSE或EventSource，都是同一个东西）等技术。优点在于这是真正的实时推送，服务端可以根据需要控制数据何时发送。缺点在于只能单向通信（后端-&gt;前端），不是全双工的。所以一般要额外使用一些请求用于前端到后端的数据传输。</li>
<li>Flash/Silverlight之类的插件：这个没啥好说的了，都装了插件了，还有什么功能不能实现的。。。缺点在于普及率不高，而且都是私有的协议，感觉都是上个世代的东西了。</li>
<li>WebSocket：真正意义上的全双工实时通信。在HTML5引入WebSocket API后，其他方案注定都会变成历史的垃圾堆。。。缺点在于各个浏览器对HTML5的支持程度不一，但这只是时间问题。另一个问题是服务端要做为WebSocket做一些改造。毕竟无论轮询还是streaming，都是基于http的，现有的各种服务端基本都能直接支持。而WebSocket则是一种全新的协议。</li>
</ul>
<p>综上，可以看出实现实时web的技术大概可以分为3类：轮询/streaming/websocket。借用一张图：<br><img src="/2017/05/10/realtime-web/websocket-1.png" alt=""></p>
<p>此外有一些概念需要澄清：</p>
<ul>
<li>长连接是一个很模糊的词，不是特指某种技术，长轮询/streaming/websocket都可以算是长连接，别纠结概念。。。长连接一般需要服务端做一些针对性的优化。</li>
<li>同样，Comet也是一个很模糊的词。Wiki中称之为“umbrella term”，指的是一种服务端推送的“风格”，也不是某种具体的技术。不过一般是基于streaming实现的。很多服务端号称支持Comet，比如tomcat，但大多是自己搞了一套私有的API，尽量不要用，否则代码就会跟特定的容器强耦合了。</li>
<li>很多人将<a href="https://zh.wikipedia.org/wiki/HTTP%E6%8C%81%E4%B9%85%E8%BF%9E%E6%8E%A5" target="_blank" rel="external">HTTP Keep Alive</a>称为长连接。但keep-alive的含义是在同一个TCP连接上可以进行多次http请求（http 1.0时代每次请求都必须打开一个新的TCP连接），跟我们所说的“实时通信”的长连接不是一个概念，注意区分。</li>
<li>经常见到一个问题：TCP是否是全双工的？答案应该是肯定的，毕竟WebSocket也是基于TCP的。但其实这个问题并不准确，TCP只是定义了传输层的协议，可以双向传输。至于是否能全双工，是取决于底层硬件的。</li>
</ul>
<h1 id="WebSocket">WebSocket</h1>
<p><a href="https://zh.wikipedia.org/wiki/WebSocket" target="_blank" rel="external">WebSocket</a>的原理其实挺简单的，socket通信在后端早就被玩了无数遍了，只不过对于web端还是个新鲜事。</p>
<p>首先要注意区分WebSocket协议和WebSocket API。<br>WebSocket是一种独立的基于TCP的通信协议，类似于HTTP、FTP。在OSI的网络模型中，它应该是属于应用层的。WebSocket协议在2011年被IETF定为标准<a href="https://tools.ietf.org/html/rfc6455" target="_blank" rel="external">RFC6455</a>，并被<a href="https://tools.ietf.org/html/rfc7936" target="_blank" rel="external">RFC7936</a>所补充规范。虽然名字叫做WebSocket，最初发明它的目的也是为了web的实时通信，但实际上WebSocket也可以用于其他环境，比如完全可以用java写一个WebSocket客户端。只不过这么用的比较少，毕竟与其用WebSocket不如直接用TCP Socket。<br>对于RFC6455，这里还有一个<a href="https://www.gitbook.com/book/chenjianlong/rfc-6455-websocket-protocol-in-chinese/details" target="_blank" rel="external">中文版</a>。<br><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket" target="_blank" rel="external">WebSocket API</a>则是由W3C制定的在浏览器中使用WebSocket协议进行通信的标准API（主要是js环境中的<code>WebSocket</code>对象），是HTML5规范中的一项，似乎目前还没有正式发布，处于<a href="https://www.w3.org/TR/websockets/" target="_blank" rel="external">候选</a>的状态，但主流的浏览器都已经支持了。</p>
<p>相对于传统的HTTP长连接，WebSocket的优点在于：</p>
<ol>
<li>真正的双向通信。而HTTP只能由客户端发起请求。</li>
<li>HTTP请求中带有大量的header，很多冗余信息，其实很多流量被浪费掉了，WebSocket则没有这个问题。</li>
<li>WebSocket协议支持各种Extension，可以实现多路复用等功能。</li>
</ol>
<p>WebSocket虽然是应用层协议，但却封装的非常“薄”，可以认为就是对TCP的一个简单封装（要不怎么叫Socket呢），在WebSocket中传输的都是字节流，至于如何解释，要交给上层去做。所以往往会有一个“子协议”的概念，比如后面将要提到的STOMP。</p>
<h2 id="握手过程">握手过程</h2>
<p>WebSocket虽然是独立于HTTP的另一种协议，但建立连接时却需要借助HTTP协议进行握手，这也是WebSocket的一个特色，利用了HTTP协议中一个特殊的header：<code>Upgrade</code>。在双方握手成功后，就跟HTTP没什么关系了，会直接在底层的TCP Socket基础上进行通信。</p>
<p>握手请求的一个例子：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="request">GET <span class="string">ws://localhost:8080/handlerA</span> HTTP/1.1</span></div><div class="line"><span class="attribute">Host</span>: <span class="string">localhost:8080</span></div><div class="line"><span class="attribute">Connection</span>: <span class="string">Upgrade</span></div><div class="line"><span class="attribute">Pragma</span>: <span class="string">no-cache</span></div><div class="line"><span class="attribute">Cache-Control</span>: <span class="string">no-cache</span></div><div class="line"><span class="attribute">Upgrade</span>: <span class="string">websocket</span></div><div class="line"><span class="attribute">Origin</span>: <span class="string">http://localhost:8080</span></div><div class="line"><span class="attribute">Sec-WebSocket-Version</span>: <span class="string">13</span></div><div class="line"><span class="attribute">Sec-WebSocket-Key</span>: <span class="string">IbMym0RGM6WulBh40amXHw==</span></div><div class="line"><span class="attribute">Sec-WebSocket-Extensions</span>: <span class="string">permessage-deflate; client_max_window_bits</span></div></pre></td></tr></table></figure>

<p>删除了一些无关的header，一些值得注意的地方：</p>
<ol>
<li>必须是GET请求。WebSocket的URI都是<code>ws://</code>开头的。</li>
<li>必须包含<code>Connection: Upgrade</code>和<code>Upgrade: websocket</code>两个header。</li>
<li><code>Sec-WebSocket-Version</code>用于指定WebSocket协议的版本，一般都是13。</li>
<li><code>Sec-WebSocket-Key</code>是一个base64编码的字符串，用于确认这是一个WebSocket握手请求而不是普通的http请求。服务端要将这个字符串解码然后和某个固定的字符串拼接后求SHA-1，然后base64编码后再返回。具体的计算过程可以参考RFC。</li>
<li><code>Sec-WebSocket-Extensions</code>用于协商能使用哪些扩展。</li>
<li>注意跨域问题，服务端必须要校验Origin字段。</li>
</ol>
<p>如果握手成功，返回<code>HTTP 101</code>响应：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="status">HTTP/1.1 <span class="number">101</span> Switching Protocols</span></div><div class="line"><span class="attribute">Server</span>: <span class="string">Apache-Coyote/1.1</span></div><div class="line"><span class="attribute">Upgrade</span>: <span class="string">websocket</span></div><div class="line"><span class="attribute">Connection</span>: <span class="string">upgrade</span></div><div class="line"><span class="attribute">Sec-WebSocket-Accept</span>: <span class="string">FcSPcCOgjs4tIy0aH9in+QmWXcg=</span></div><div class="line"><span class="attribute">Sec-WebSocket-Extensions</span>: <span class="string">permessage-deflate;client_max_window_bits=15</span></div><div class="line"><span class="attribute">Date</span>: <span class="string">Tue, 21 Mar 2017 06:17:04 GMT</span></div></pre></td></tr></table></figure>

<p>注意其中的<code>Sec-WebSocket-Accept</code>字段，就是服务端根据<code>Sec-WebSocket-Key</code>计算后的值。客户端必须校验这个值，校验通过才能建立连接。</p>
<p>为啥WebSocket会使用这种设计？个人猜测，借助HTTP进行握手有一些好处：</p>
<ol>
<li>这样设计WebSocket会使用和HTTP相同的端口（80或443），可以穿过很多防火墙。</li>
<li>直接使用HTTP header中已有的信息，比如Cookie。很多服务端都会在Cookie中种一个sessionid，WebSocket可以直接使用这个sessionid去识别不同的会话，Spring就是这么搞的。</li>
<li>暂时没想出来。。。</li>
</ol>
<p>话说HTTP都是快20年之前的东西了，就有这种设计，也真是NB。。。</p>
<h2 id="WebSocket_Frame">WebSocket Frame</h2>
<p>握手成功后，双方就可以切换到WebSocket协议进行通信了。<br>WebSocket中数据交换的基本单位是“帧（Frame）”，其格式参考RFC中的第五章：<a href="https://tools.ietf.org/html/rfc6455#section-5" target="_blank" rel="external">Data Framing</a>。</p>
<p><img src="/2017/05/10/realtime-web/websocket-2.png" alt=""></p>
<p>可以对比TCP的header去理解。几个值得注意的地方：</p>
<ol>
<li>FIN位用于指示是最后一个帧，在分片的情况下才有用。</li>
<li>OPCODE字段用于指示帧的类型，4位，所以最多有16种帧。但其实很多没用到：<ul>
<li>%x0 代表一个继续帧</li>
<li>%x1 代表一个文本帧</li>
<li>%x2 代表一个二进制帧</li>
<li>%x3-7 保留用于未来的非控制帧</li>
<li>%x8 代表连接关闭</li>
<li>%x9 代表ping</li>
<li>%xA 代表pong</li>
<li>%xB-F 保留用于未来的控制帧</li>
</ul>
</li>
<li>客户端发送数据时必须要有mask，不知道是为啥，也许是出于安全考虑？</li>
<li>payload len是变长的，可能是7 bits、7+16 bits或者 7+64 bits。</li>
<li>payload data由部分组成，分别是“扩展数据（Extension Data）”和“应用数据（Application Data）”。还记得握手时的<code>Sec-WebSocket-Extensions</code>么，如果双方同意使用某个扩展，才会有扩展数据。</li>
</ol>
<p>从上文中可以看出，WebSocket将帧分为两类：</p>
<p>控制帧：</p>
<ul>
<li>Close：用于关闭当前WebSocket连接。接收方也要响应一个Close帧，然后关闭TCP连接。</li>
<li>Ping/Pong：用于维持心跳。当收到一个Ping帧时，必须在响应中发送一个Pong帧。</li>
</ul>
<p>数据帧：</p>
<ul>
<li>Text：说明payload data是UTF-8编码的字节流。</li>
<li>Binary：字节流如何解释完全交给上层。</li>
</ul>
<p>从数据帧的定义可以看出，WebSocket协议中定义好的“逻辑”很少，很多都需要上层应用去补充。所以说WebSocket是对TCP非常薄的一层封装，往往要搭配“子协议”使用。</p>
<p>话说，在看RFC的过程中还了解到一个有趣的东西：<a href="https://zh.wikipedia.org/wiki/%E6%89%A9%E5%85%85%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F" target="_blank" rel="external">ABNF</a>，可以用于形式化的描述网络协议，数学中还是有挺多好玩的东西的。</p>
<h2 id="Extension">Extension</h2>
<p>WebSocket协议定义了<a href="https://tools.ietf.org/html/rfc6455#section-9" target="_blank" rel="external">Extension</a>，在frame中也定义了Extension Data，决定了服务端如何解释Application Data，进而可以对原有的协议做一些扩展，但却没有定义任何具体的Extension，RFC中的原文是：“This document doesn’t define any extension, but implementations MAY use extensions defined separately.”。</p>
<p>之前说过WebSocket可以支持多路复用，就是以Extension的形式出现的，但似乎还只是草案，见<a href="https://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01" target="_blank" rel="external">这里</a>，似乎实际中也没什么应用。客户端倒是<a href="https://github.com/sockjs/websocket-multiplex" target="_blank" rel="external">有个库</a>可以支持，但服务端似乎大多不支持Extension。</p>
<p>也许以后才会出现更多的Extension吧。</p>
<h2 id="客户端代码">客户端代码</h2>
<p>如果直接使用原生的WebSocket API，大概是这个样子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 注意url必须是ws://开头的</span></div><div class="line"><span class="keyword">var</span> host = <span class="string">'ws://localhost:8080/handlerA'</span>;</div><div class="line"><span class="comment">// 这个WebSocket是浏览器内置的对象，必须要浏览器支持</span></div><div class="line"><span class="keyword">var</span> ws = <span class="keyword">new</span> WebSocket(host);</div><div class="line"></div><div class="line">ws.binaryType = <span class="string">'arraybuffer'</span>;</div><div class="line">ws.onopen = <span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{on_open(e)};</div><div class="line">ws.onmessage = <span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{on_message(e)};</div><div class="line">ws.onclose = <span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{on_close(e)};</div></pre></td></tr></table></figure>

<h1 id="SockJS">SockJS</h1>
<p><a href="https://github.com/sockjs/sockjs-client" target="_blank" rel="external">SockJS</a>本质上是一种fallback机制，由于不是所有的浏览器都支持WebSocket，所以在某些环境下我们必须要选择Streaming或轮询的方案，而人工选择这些方案实在太麻烦了，要兼容的情况也太多。SockJS致力于隐藏这些复杂性，自动根据服务端和客户端的情况选择合适的方案，然后对外提供统一的API，应用层无需考虑具体的传输方式。<br>不过，fallback可能有一些限制，原文：“for some fallbacks transports it is not possible to open more than one connection at a time to a single server.”</p>
<p>总的来说SockJS支持3类传输方式（就是上面讲过的），优先级依次降低：</p>
<ol>
<li>WebSocket，最优选择</li>
<li>Streaming，如果不支持CORS跨域，还要用iframe+<a href="http://www.cnblogs.com/dolphinX/p/3464056.html" target="_blank" rel="external">postMessage</a>之类的去实现跨域</li>
<li>Polling，最传统的轮询方式</li>
</ol>
<p><img src="/2017/05/10/realtime-web/websocket-3.png" alt=""></p>
<p>客户端代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 前提是先引入sockjs-client</span></div><div class="line"><span class="comment">// sockjs的url必须是http/https</span></div><div class="line"><span class="keyword">var</span> host = <span class="string">'http://localhost:8080/sockjs/handlerA'</span>;</div><div class="line"><span class="keyword">var</span> ws = <span class="keyword">new</span> SockJS(host);</div><div class="line"></div><div class="line"><span class="comment">// sockjs的api跟原生的websocket是基本一致的，它的目标就是提供一致的编码体验</span></div><div class="line">ws.binaryType = <span class="string">'arraybuffer'</span>;</div><div class="line">ws.onopen = <span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{on_open(e)};</div><div class="line">ws.onmessage = <span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{on_message(e)};</div><div class="line">ws.onclose = <span class="function"><span class="keyword">function</span><span class="params">(e)</span></span>{on_close(e)};</div></pre></td></tr></table></figure>

<p>SockJS建立连接时会先请求一次<code>/info</code>接口，比如你要连接的url是<code>/sockjs/handlerA</code>，就会先请求<code>/sockjs/handlerA/info</code>（其实就是一次普通的GET请求）：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="request">GET <span class="string">/sockjs/handlerA/info?t=1494472167320</span> HTTP/1.1</span></div><div class="line"><span class="attribute">Host</span>: <span class="string">localhost:8080</span></div><div class="line"><span class="attribute">Connection</span>: <span class="string">keep-alive</span></div><div class="line"><span class="attribute">Pragma</span>: <span class="string">no-cache</span></div><div class="line"><span class="attribute">Cache-Control</span>: <span class="string">no-cache</span></div><div class="line"><span class="attribute">Accept</span>: <span class="string">*/*</span></div><div class="line"><span class="attribute">Referer</span>: <span class="string">http://localhost:8080/index.html</span></div><div class="line"><span class="attribute">Accept-Encoding</span>: <span class="string">gzip, deflate, sdch, br</span></div><div class="line"><span class="attribute">Accept-Language</span>: <span class="string">zh-CN,zh;q=0.8,en;q=0.6</span></div></pre></td></tr></table></figure>

<p>服务端会返回一些信息，表示服务端支持哪些传输方式：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">{"<span class="attribute">entropy</span>":<span class="value">-<span class="number">490091108</span></span>,"<span class="attribute">origins</span>":<span class="value">[<span class="string">"*:*"</span>]</span>,"<span class="attribute">cookie_needed</span>":<span class="value"><span class="literal">true</span></span>,"<span class="attribute">websocket</span>":<span class="value"><span class="literal">true</span></span>}</div></pre></td></tr></table></figure>

<p>SockJS会结合服务端的信息和客户端的环境，选择合适的传输方式，选择过程还有点复杂的，最多可能需要3~4轮的尝试，再加上DNS解析的耗时，所以建立连接可能会稍微慢一点。<br>以下只讨论使用WebSocket的情况。</p>
<p>如果服务端和客户端都支持WebSocket，之后的通信会通过一个特殊的URI，类似这种<code>ws://localhost:8080/sockjs/handlerA/489/cn05bzby/websocket</code>，这个url的格式是：<code>ws://host:port/myEndpoint/{server-id}/{session-id}/{transport}</code>，其中：</p>
<ul>
<li><code>{server-id}</code> - useful for routing requests in a cluster but not used otherwise.</li>
<li><code>{session-id}</code> - correlates HTTP requests belonging to a SockJS session.</li>
<li><code>{transport}</code> - indicates the transport type, e.g. “websocket”, “xhr-streaming”, etc.</li>
</ul>
<p>不过目测server-id和session-id都是随机生成的，每次连接都会改变，不知道有啥用。也许是在负载均衡中会用到，Spring的文档中专门讲到过WebSocket的负载均衡。</p>
<p>sockjs其实会对发送的信息做一些修改，打开chrome的调试工具就可以看到。比如客户端发送的信息会被包装为<code>a[&quot;message1&quot;,&quot;message2&quot;]</code>（JSON数组）的形式，还有字母<code>h</code>作为心跳（每25秒一次），<code>o</code>表示连接已建立，<code>c</code>表示关闭连接之类的，也算是一种简单的子协议吧。</p>
<p>参考资料：<br><a href="https://github.com/sockjs/sockjs-client/wiki" target="_blank" rel="external">https://github.com/sockjs/sockjs-client/wiki</a></p>
<h1 id="STOMP">STOMP</h1>
<p><a href="https://stomp.github.io/" target="_blank" rel="external">STOMP</a>其实跟WebSocket没啥必然关系，它是一种mq协议，最初是设计出来给各种脚本语言用的，跟它对等的应该是<a href="https://www.amqp.org/" target="_blank" rel="external">AMQP</a>、<a href="http://mqtt.org/" target="_blank" rel="external">MQTT</a>等协议。由于工作原因，我之前还研究过一点MQTT、XMPP之类的。<br>如果说AMQP的特点是“强大”（企业级的mq规范，提出了各种model），MQTT的特点是“紧凑”（尤其适用于嵌入式设备），那STOMP的特点就是“简单”。正如它的名字（Simple Text Orientated Messaging Protocol），它的设计思路一直就是保持简单的协议、简单的API。而且它是一种Text-Based Protocol，跟HTTP类似，可读性非常好，也非常易于跨平台。只要你的语言提供Socket操作，你都能很快的实现一个STOMP客户端。甚至直接用telnet当作客户端也可以。</p>
<p>但简单也就意味着功能上要有所取舍。STOMP中完全没有AMQP中的queue、exchange等概念，换句话说，只有publish-subscribe模式，如果想要更灵活的路由和处理逻辑，就会有点麻烦。</p>
<p>WebSocket通信中最常用的子协议就是STOMP了，当然也有<a href="https://www.rabbitmq.com/web-mqtt.html" target="_blank" rel="external">用MQTT</a>的，你也可以用自己的私有协议，但用STOMP的好处在于：</p>
<ul>
<li>STOMP的可靠性已经经过广泛验证</li>
<li>支持STOMP的服务端很多：RabbitMQ、ActiveMQ等等</li>
<li>浏览器上已经有了可用的客户端：<a href="https://github.com/jmesnil/stomp-websocket" target="_blank" rel="external">stomp.js</a></li>
</ul>
<p>STOMP 1.1的规范：<a href="http://stomp.github.io/stomp-specification-1.1.html" target="_blank" rel="external">http://stomp.github.io/stomp-specification-1.1.html</a><br>stomp.js的文档：<a href="http://jmesnil.net/stomp-websocket/doc/" target="_blank" rel="external">http://jmesnil.net/stomp-websocket/doc/</a></p>
<p>话说STOMP协议真的是简单易懂，很快就能看完，自己实现一套估计也不费劲。相比之下看各种RFC真是痛苦。。。</p>
<p>STOMP中的消息都被抽象为“帧”（有点类似AMQP中message的概念），帧的格式和HTTP非常类似，分为command、header、body三部份。其中比较重要的就是SUBSCRIBE/SEND/MESSAGE帧。SUBSCIRBE帧用于订阅某个destination，SEND帧用于发送数据，MESSAGE帧用于从服务端接收数据。尤其注意下其中的destination header，有点像传统mq中的topic。STOMP不限定destination的格式，可以是任意格式字符串，由服务端去解释，不过一般都是<code>/a/b</code>这种类似路径的格式。</p>
<p>此外，STOMP还支持认证、事务、ACK之类的机制，不再赘述，详情请参考规范。</p>
<p>一些客户端代码示例：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 获取stomp对象的几种方式</span></div><div class="line">Stomp.client(url)  </div><div class="line">Stomp.over(ws)</div><div class="line"></div><div class="line"><span class="comment">// 连接服务端的几种方式</span></div><div class="line">client.connect(login, passcode, connectCallback);</div><div class="line">client.connect(login, passcode, connectCallback, errorCallback);</div><div class="line">client.connect(login, passcode, connectCallback, errorCallback, host);</div><div class="line">client.connect(headers, connectCallback);</div><div class="line">client.connect(headers, connectCallback, errorCallback);</div><div class="line"></div><div class="line">client.disconnect(<span class="function"><span class="keyword">function</span><span class="params">()</span> </span>{</div><div class="line">    alert(<span class="string">"See you next time!"</span>);</div><div class="line">};</div><div class="line"></div><div class="line">client.heartbeat.outgoing = <span class="number">20000</span>; <span class="comment">// 设置心跳</span></div><div class="line">client.heartbeat.incoming = <span class="number">0</span>;</div><div class="line"></div><div class="line"><span class="comment">// client同时是生产者和消费者</span></div><div class="line">client.send(<span class="string">"/queue/test"</span>, {priority: <span class="number">9</span>}, <span class="string">"Hello, STOMP"</span>);  <span class="comment">// 发送消息</span></div><div class="line">client.send(destination, {}, body);</div><div class="line"></div><div class="line"><span class="comment">// 订阅消息</span></div><div class="line"><span class="keyword">var</span> subscription = client.subscribe(<span class="string">"/queue/test"</span>, callback);</div><div class="line"><span class="comment">// 取消订阅</span></div><div class="line">subscription.unsubscribe();</div><div class="line"></div><div class="line"><span class="comment">// start the transaction</span></div><div class="line"><span class="keyword">var</span> tx = client.begin();</div><div class="line"><span class="comment">// send the message in a transaction</span></div><div class="line">client.send(<span class="string">"/queue/test"</span>, {transaction: tx.id}, <span class="string">"message in a transaction"</span>);</div><div class="line"><span class="comment">// commit the transaction to effectively send the message</span></div><div class="line">tx.commit();</div></pre></td></tr></table></figure>

<h1 id="WebSocket_with_Spring">WebSocket with Spring</h1>
<p>Spring 4开始支持WebSocket，相关配置参考<a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html" target="_blank" rel="external">文档</a>，我这边也有一个<a href="https://github.com/jiangxy/websocket-debug-tool/tree/master/java" target="_blank" rel="external">例子</a>。</p>
<p>注意添加必要的依赖：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="title">dependency</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">groupId</span>&gt;</span>org.springframework<span class="tag">&lt;/<span class="title">groupId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">artifactId</span>&gt;</span>spring-messaging<span class="tag">&lt;/<span class="title">artifactId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">version</span>&gt;</span>4.2.6.RELEASE<span class="tag">&lt;/<span class="title">version</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="title">dependency</span>&gt;</span></div><div class="line"><span class="tag">&lt;<span class="title">dependency</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">groupId</span>&gt;</span>org.springframework<span class="tag">&lt;/<span class="title">groupId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">artifactId</span>&gt;</span>spring-websocket<span class="tag">&lt;/<span class="title">artifactId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">version</span>&gt;</span>4.2.6.RELEASE<span class="tag">&lt;/<span class="title">version</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="title">dependency</span>&gt;</span></div><div class="line"><span class="tag">&lt;<span class="title">dependency</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">groupId</span>&gt;</span>javax.servlet<span class="tag">&lt;/<span class="title">groupId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">artifactId</span>&gt;</span>javax.servlet-api<span class="tag">&lt;/<span class="title">artifactId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">version</span>&gt;</span>3.1.0<span class="tag">&lt;/<span class="title">version</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">scope</span>&gt;</span>provided<span class="tag">&lt;/<span class="title">scope</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="title">dependency</span>&gt;</span></div><div class="line"><span class="tag">&lt;<span class="title">dependency</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">groupId</span>&gt;</span>javax.websocket<span class="tag">&lt;/<span class="title">groupId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">artifactId</span>&gt;</span>javax.websocket-api<span class="tag">&lt;/<span class="title">artifactId</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">version</span>&gt;</span>1.1<span class="tag">&lt;/<span class="title">version</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="title">scope</span>&gt;</span>provided<span class="tag">&lt;/<span class="title">scope</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="title">dependency</span>&gt;</span></div></pre></td></tr></table></figure>

<p>Spring对WebSocket的支持还是挺全面的，支持直接使用low-level API，支持sockjs，也支持STOMP，跨域/Handshake Interceptor之类的也都支持。</p>
<p>值得注意的地方：</p>
<ol>
<li>对容器版本有要求，低版本的容器中很可能无法使用。目前只支持Tomcat 7.0.47+、Jetty 9.1+、GlassFish 4.1+、WebLogic 12.1.3+，对于不支持的容器，要自己实现RequestUpgradeStrategy和WebSocketHttpRequestHandler等。</li>
<li>Spring提供了很多可以配置的参数，比如线程池大小/buffer大小/消息大小限制/心跳等等，但很多参数文档中只是提了一下，没说怎么配。。。我还要去翻dtd文件或者google才知道如何配置。</li>
<li>Spring提供了java版的SockJS client和STOMP client，一般用于调试。</li>
<li>可以自动识别http或sockjs的session。</li>
<li>对于websocket，spring没有专门的认证机制，而是直接用http的认证。</li>
<li>可以用ApplicationListener监听各种事件，非常有用。</li>
<li>专门为websocket新增了一个scope：<code>@Scope(scopeName = &quot;websocket&quot;)</code>，可以为每个websocket session单独组装一个bean。</li>
</ol>
<p>在使用STOMP时，Spring会作为一个简单的in-memory borker存在，但也加入了一些自己特殊的逻辑，关键是要理解消息的流动过程：</p>
<p><img src="/2017/05/10/realtime-web/websocket-4.png" alt=""></p>
<p>可以看出Spring的路由策略非常简单，只是基于消息的destination做前缀匹配。某些消息会直接流向broker，另一些消息则会流向Controller方法，经过一些业务逻辑处理后再流向broker。进入broker的消息则会被被直接发给客户端。Spring提供了非常多的注解用于消息的处理，详见文档。如果有些特殊逻辑不能在Controller中处理，也可以使用Channel Interceptor，上图中的request/broker/response三个channel都可以配置。</p>
<p>Spring默认会使用一个in-memory broker，但是也可以配置为外部的RabbitMQ、ActiveMQ等，称作relay broker，更加利于扩展和维护。</p>
<h1 id="其他">其他</h1>
<p>关于EventSource的更多资料：<br><a href="http://javascript.ruanyifeng.com/htmlapi/eventsource.html" target="_blank" rel="external">http://javascript.ruanyifeng.com/htmlapi/eventsource.html</a><br><a href="http://stackoverflow.com/questions/5195452/websockets-vs-server-sent-events-eventsource" target="_blank" rel="external">http://stackoverflow.com/questions/5195452/websockets-vs-server-sent-events-eventsource</a></p>
<p>RabbitMQ支持的协议类型：<br><a href="https://www.rabbitmq.com/protocols.html" target="_blank" rel="external">https://www.rabbitmq.com/protocols.html</a></p>
<p>几种MQ协议的对比：<br><a href="https://blogs.vmware.com/vfabric/2013/02/choosing-your-messaging-protocol-amqp-mqtt-or-stomp.html" target="_blank" rel="external">https://blogs.vmware.com/vfabric/2013/02/choosing-your-messaging-protocol-amqp-mqtt-or-stomp.html</a></p>
<p>google的过程中经常接触到所谓的wire-level protocol，感觉这也是一个有些模糊的词。wire-level protocol不一定是binary的，也可能是text的。根据wiki的说法，SOAP也算是wire-level。个人感觉，只要协议中规定了数据中如何在网络中传输（关键是理解wire的概念），就算是wire-level protocol。与之相对的反义词是API，比如JMS、JDBC，只规定如何使用，不规定数据如何传输。</p>
<p>为啥会去研究websocket呢，因为直播中的弹幕有用到。顺便也研究了下直播常用的协议：RTMP（flash），HLS（苹果），RTP/RTCP（一般用于视频电话之类的）。我们在H5端用的是HLS，兼容性好，但是延迟比较大。为了克服这个缺点，有人提出可以用WebSocket传输视频帧，然后在canvas上绘制，居然还真有这种案例，感觉这是邪路啊。。。不过说不定像<a href="https://github.com/Flipboard/react-canvas" target="_blank" rel="external">react-canvas</a>（这货star都破万了）一样，意外的好用。。。</p>
<p>顺便还复习了一下TCP，感慨于TCP的各种精妙设计，ACK/滑动窗口/重发控制/流控/延迟应答等等，很多时候我们都只是在重复前人啊。。。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>前段时间研究了下WebSocket，趁着还没忘总结下。</p>
]]>
    
    </summary>
    
      <category term="websocket" scheme="http://jxy.me/tags/websocket/"/>
    
      <category term="spring" scheme="http://jxy.me/tags/spring/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[拖更已是常态]]></title>
    <link href="http://jxy.me/2017/04/02/lazy-busy-lazy/"/>
    <id>http://jxy.me/2017/04/02/lazy-busy-lazy/</id>
    <published>2017-04-02T14:54:51.000Z</published>
    <updated>2017-04-13T16:56:47.000Z</updated>
    <content type="html"><![CDATA[<p>年终总结时，我曾痛下决心，每月至少写一篇，结果。。。</p>
<a id="more"></a>
<p>当然可以找很多理由，比如工作啊，比如生活啊，但说到底其实就是拖延症发作。<br>其实啊，人活在世，槽点总是会不断积累的，只不过放着放着就忘了。</p>
<p>不过最近确实也没研究啥新玩意，还是在做着各种各样的“日常”，毕竟是打杂工程师嘛。倒是我的<a href="https://github.com/jiangxy/react-antd-admin" target="_blank" rel="external">react后台</a>写的很欢快，发了几个版本，渐渐有点样子了，收到star还是挺开心的，也收到了一些正面的反馈。已经在前端的大坑里越陷越深了。。。<br>对于非技术方面的思考倒是很多，而且这比研究技术难得多。<br>我tm是不是中年危机了啊。。。</p>
<p>不管怎样，偶尔停下脚步，收拾下心情，挺好的。我还保留着独立思考的能力，略感欣慰。<br>虽然我不能保证我的思考都是正确的，博君一笑罢了。</p>
<h1 id="关于title">关于title</h1>
<p>所谓title，或者说level/头衔/级别，就是“高级/资深/总监”之类的“名号”，国外可能是“junior/senior/director/principal”之类的。这玩意大家都挺在意的，但也是很容易有槽点的一个东西。</p>
<p>起因是我最近看了下脉脉，被一众高title闪瞎了眼。。。让我有一种跻身上流社会的错觉。。。这个经理那个总监，这个CXO那个创始人的。。。倒是前老大就简简单单写着“XX司技术”，让我觉得很亲切。</p>
<p>这让我想起某次面试，候选人的简历显示他曾是某个公司的VP。看到简历我就吓尿了，这种级别的轮得到我来面试么？结果聊下了发现，水份有点大。。。说技术吧，他已经脱离一线开发很久了，很多东西只知道个概念，甚至概念也说不清；说管理吧，其实他前公司的规模很小，而且互联网公司很少会找一个纯粹的“管理者”，大多是所谓的“技术管理者（Tech Leader）”。虽然我看在党国的份上想拉他一把，但他二面还是挂掉了。。。</p>
<p>所以说，title这种东西，有时候可能有水份，只能起一个参考作用，或者说，只有统计学意义。同一家公司高level的人一般整体上能力优于低level的，但具体到某些个体上就未必了。<br>不同公司间的级别也很难对比，google/FB的senior到国内可能就是总监了；甚至同一个公司的不同部门间级别也不能对比，做业务的部门往往更容易拿到结果，做基础技术的部门就可能比较悲催，比如dba/运维。。。具体到个人就更不能一概而论，跟个人际遇有很大关系，比如加入的早晚。。。升级也是个有点玄学的事情。</p>
<p>同理，想靠title服众也是很难的，还是要看实力。</p>
<p>但有一点毫无疑问，title/level是越高越好的，不管是出于什么目的，为了虚荣心也好，为了待遇也罢，人们总是会故意/非故意的美化自己，于是有了各种各样“注水”行为。。。简历可以美化，项目经历可以夸大，技术栈略懂皮毛就可以到处吹逼，甚至某<a href="http://github.com" target="_blank" rel="external">大型同性交友网站</a>的经历都可以造假。。。前段时间v2ex不是就爆出了某人号称参与了各种大型开源项目，看profile各种NB，实际上所有PR都只是改了改文档/注释之类的。。。</p>
<p>我不能认同这种注水行为，但我非常理解。因为<strong>这个社会早已经不是埋头做事的年代了，自我包装/自我营销是非常重要的</strong>，甚至你说是“忽悠”也可以，没有人可以置身事外，虽然这有点悲哀。当大家都在注水的时候，如果你不参与进来，就会被你的圈子、被业界、甚至被社会抛下，这就是所谓的劣币驱逐良币吧。就好比，工作三十年都不如买套房挣钱快，还有谁愿意工作啊，都炒房去算了。<br>单纯靠技术获得成功是非常难的。人脉广/会做ppt才是硬道理。。。我不否认这些“soft tech”的重要性，但是不是有点本末倒置了。也许从单个事例来看这不是什么大问题，最多是用人不察罢了；但整体上看，这会潜移默化影响所有人的价值观，就不知是福是祸了。</p>
<p>曾经在cc98（浙大的内网论坛）上有一个帖子，题目大概是这样的：《如果你不了解XX和XX，就只能一辈子是个工程师》。浓浓的朋友圈画风有木有。。。某老年dota男主播2009（有段时间我还经常打dota的）也曾在节目中感慨过：自己的父亲不会营销自己，所以只能做个工程师，没什么成就（大意）。我想说“工程师”跟你们什么仇什么怨。。。<br>但不得不承认，目前这个社会的主流价值观就是这样的（以后会不会变化我不知道）。想取得世俗所定义的“成功”（当上CEO赢取白富美走上人生巅峰之类的），就不得不跟随这种价值观。可能我有点悲观，但我觉得现实一定比我写的还要残酷。。。这很像经济学中所说的泡沫和通货膨胀（专有名词：通P膨胀），在膨胀的过程中，上层会不断掠夺底层，马太效应愈加明显。反正别像某打工皇帝一样把泡泡吹爆了就好。。。</p>
<p>不过话说回来，我并不是想说“肉食者鄙”之类的。决定你的level/title的，不是你的技术，而是你的<strong>价值</strong>。对公司而言，如何衡量你的价值？写代码/做业务当然是价值，解决其他人不能解决的问题/找到前进方向/日常管理也是价值，说的夸张点如果你靠黑魔法之类的能帮公司招来各种大牛，就算白养着你不干活公司也不亏啊。。。所以说价值有很多种，不同环境中的衡量标准也不一样，技术只是其中一部分罢了。<br>搞技术的人很容易陷入所谓的“技术万能论”，觉得只要自己技术足够NB就万事OK了，然后自然就会变成人生赢家。不能否认有这种大神，比如Linus。但大多数人达不到这个高度，更多的是各种反面教材，请自行google<a href="https://www.google.com.hk/?gws_rd=ssl#newwindow=1&amp;safe=strict&amp;q=%E5%9B%9B%E5%A4%A7%E9%AD%94%E9%81%93%E7%BC%96%E7%A8%8B%E5%A4%A9%E7%8E%8B" target="_blank" rel="external">四大魔道编程天王</a>（不知哪位仁兄起的这个名号）。。。</p>
<h1 id="关于“贴标签”">关于“贴标签”</h1>
<p>由title衍生而来的一些想法。<br>“贴标签”/“扣帽子”，说的都是一个事，这个很难用语言去定义。如果不了解什么是“贴标签”，去知乎看看各种涉及到女权的话题，如何稍有不慎就会被喷成“直男癌”，然后就明白了。。。<br>title就是一种简单粗暴的标签，可以快速的让你“了解”一个人，虽然这种“了解”非常不靠谱。</p>
<p>标签是一种非常好用的东西，可以快速的了解其他人。人们往往会根据某个标签的“共性”去推断一个人的方方面面。比如对于“程序员”，大家往往会觉得“逻辑性强/情商低/挣的多/死的早”。。。虽然这些特征也只有统计学意义，具体到某个个体上未必是这样，但一般也不会偏差太多。标签还可以快速的让他人了解自己，通过给自己贴标签的方式，让对方根据这些标签去推断。想想相亲时都是如何做自我介绍的。。。虽然这不是什么好办法，而且对方的理解可能有偏差，但确实是最省力、最快速的方式。<br>可以说，贴标签就是人的本能，每个人都在忙着给别人贴标签，也在给自己贴标签。</p>
<p>但标签也可能是个很危险的东西，因为有一些人会不负责任的评判他人，滥用标签。可能是无心的，只是为了求个存在感，或者是为了显摆，也许“评判他人”这种行为有一种高高在上指点江山的快感吧；但也有可能是恶意的，比如上面说的“直男癌”，只是用来攻击意见不同的人的工具。这就有点批斗的感觉了。我最近碰到的一个事例是“这人技术不行”，言下之意“他不如我”。额，只是业务领域不同而已，这个评价未免有点过了。还好我双方都了解一些。。。</p>
<p>我更推崇所谓的“no judge”，不要随意的评判一个人，不管什么身份地位，这是对人基本的尊重吧。一旦对某个人下了“判决”，那固有的印象就很难改变了。no judge ≠ 不能评价别人，而是说评价时要更谨慎，基于客观事实而不是主观臆测，同时对自己做出的评价要负责。<br>为啥网络喷子可以肆无忌惮的给其他人扣帽子，就是因为他们不用为说过的话负责。。。<br>这个话题如果继续讨论下去，都可以上升到人性的高度了，那就不是我能想明白的了。</p>
<p>我也要时刻注意自己的言行，谨记。</p>
<h1 id="关于业务">关于业务</h1>
<p>业务代码写的多了，心中总是难免忐忑。</p>
<p>在网易，我可以4年只干一件事，或者说至少是认准一个方向，把hadoop研究明白就可以了。不管是做BI、做数据、做运维、做开发，都是围绕着hadoop。<br>但是离开之后，细细数来，直接参与的已经做过4、5个业务，算上间接参与的就更多了，“打杂工程师”可不是随便说说的。不同业务方向间基本没什么关联。关键是没有“做起来”的，这就有点焦虑了。。。总是热火朝天干一段时间，但没什么起色，然后慢慢冷寂。。。当然有些业务是不能急于求成的，是需要“熬”的，慢慢去培养和运营。但是如果经过三五年才失败，才被判死刑，那中间这段时间岂不是荒废了，怎么跟参与项目的人交代？失败不可怕，关键失败后总要留下点什么。是技术？经验？资历？我也不知道。。。<br>就像我之前说的，“苦劳”这种东西是不存在的，how cruel this world。</p>
<p>从公司的角度来说，在没有明确业务模式的情况下，确实是要不断的快速尝试，fail-fast。因为一旦停下来必定就会挂掉，动一动还有可能找到方向。个人如何在这种不断变化的尝试中成长，才是我们需要思考的。</p>
<p>有这种焦虑的人应该还挺多的。但就像我<a href="/2016/10/24/1024-festival/">之前</a>说的，业务和技术从来不是对立的。这就是个围城，单纯做业务的觉得自己一直在拧螺丝，技术没什么成长，担忧未来；单纯做技术的又觉得自己技能过于单一，缺少业务场景，很难做出成绩拿到结果。这两种状态我或多或少都经历过。最好的结果是二者能互相促进，业务的发展能推动技术，技术的发展又能给业务更多想象空间。但如何达成这种正向的循环，因人而异，只能自己去不断摸索。<br>从个人成长角度来说，技术为本，兼顾某些业务领域，同时也要能快速理解其他领域的业务模式，再懂点管理，是最好的状态了吧。当然这只是我一家之言，未必是对的。</p>
<p>随着云计算的发展，现在越来越多的东西变成了基础设施（Infrastructure）：db、mq、hadoop、redis，作为一种服务提供给外部用户，降低了开发成本，也让分工更加明确了。分工的好处就是可以提升生产效率，用户完全不用了解细节；但也容易把人变成螺丝钉，缺少一种全局的视野，毕竟懒惰是人类天性（这我深有体会）。。。</p>
<p>话说回来，一块业务的成功与否，往往也是需要天时地利人和的，如何找到靠谱的业务方向也是个技术活，真正NB的人往往是“创造”需求而不是“发现”需求。而我们一般只能在“人和”上下功夫，或者说，“执行力”。这样就算失败，至少可以说是“非战之罪”。就像打台球一样，就算球就在洞口，就算我能计算好所有球碰撞后的行进路线，但是tmd打出去的白球总是歪啊，计算的再好又有毛线用。。。</p>
<h1 id="成长没有捷径">成长没有捷径</h1>
<p>看到一些文章，感触颇深，摘录下。</p>
<p><a href="http://www.infoq.com/cn/news/2016/12/architect-CTO-growth-not-easy" target="_blank" rel="external">http://www.infoq.com/cn/news/2016/12/architect-CTO-growth-not-easy</a></p>
<blockquote class="blockquote-center">成长是漫长的，充满了点滴细节，没有一蹴而就，但是智慧和经验却可以复用<br><br>独立思考，推动变化及拿到结果的能力和愿望会变的更为重要<br></blockquote>

<p><a href="http://www.infoq.com/cn/articles/road-of-the-development-of-technical-people" target="_blank" rel="external">http://www.infoq.com/cn/articles/road-of-the-development-of-technical-people</a></p>
<blockquote class="blockquote-center">在你事业的上升期，你需要更多的软技能<br><br>你还要明白在职场里的几个冷酷的事实：你要开始要关心并处理复杂的人事<br>code is cheap，talk is the matter<br></blockquote>

<h1 id="杂谈">杂谈</h1>
<p>一些零散的想法。</p>
<ol>
<li>认识自己才是最困难的事。what you are you do not see, what you see is your shadow.</li>
<li>我这人应该算是比较谨慎保守的，做事之前会想的很多，考虑的很全面再动手。缺点是有些时候想的太多了，有些踌躇不前。我也见过很多人干劲十足却很少思考，说是横冲直撞也不为过。孰优孰劣？没有定论。还是一个“度”的把握。</li>
<li>在网易时某前辈说过一句至理名言：“能重启解决的问题都不是问题”。虽是戏谑之语，我却想借用一下：能加班解决的问题都不是问题，能用技术解决的问题也都不是问题。那啥是问题？找不到方向才是问题。</li>
<li>如何说服他人一起做某件事？说白了很简单，上策是让双方有共同的利益，下策就是大棒+胡萝卜。。。做事的态度（自主推动 vs 公事公办）对结果影响很大。</li>
<li>忽悠其他人之前往往要先忽悠自己。。。</li>
<li>发现前端项目在github上更容易获得star，毕竟做的是看得见的事。但就目前而言，后端的天花板更高。</li>
<li>慎用fixme。。。有多少次fixme变成了遗留问题，这就是技术债务啊。</li>
</ol>
<p>扯了这么多，也只是我自己随便发发牢骚而已。很多事情只能是如鱼饮水，冷暖自知。我说出来其他人也未必能感同身受。不过至少我吐槽之后感觉轻松多了。。。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>年终总结时，我曾痛下决心，每月至少写一篇，结果。。。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[时间倏忽而逝]]></title>
    <link href="http://jxy.me/2017/01/15/2017-and-redux/"/>
    <id>http://jxy.me/2017/01/15/2017-and-redux/</id>
    <published>2017-01-14T17:10:04.000Z</published>
    <updated>2017-01-30T13:15:04.000Z</updated>
    <content type="html"><![CDATA[<p><center>转眼已是经年</center><br><a id="more"></a></p>
<hr>

<p>2017如期而至，虽然早有准备，但总是难免有点点伤感。不为了什么，只是单纯感慨时光匆匆而已。花相似，人不同。果然我是老了么。。。<br>至少我觉得自己还走在大致正确的方向上，略感欣慰。<br>不过也说不准，说不定明年就回老家种地去了呢。。。</p>
<p>本文将继续贯彻落实XX届X中X会上确立的<strong>形散神也散</strong>的重要精神，有一些吐槽，也有一些最近研究react/redux的心得，还有各种乱七八糟的东西。</p>
<h1 id="CPS变换">CPS变换</h1>
<p>最开始听说cps变换是从<a href="https://www.zhihu.com/question/20822815" target="_blank" rel="external">某个知乎帖子</a>，当时只是不明觉历。最近在研究redux，接触了一些函数式编程相关的思想，机缘巧合下又遇到这个问题，于是研究下。</p>
<p>简单点说，<a href="https://en.wikipedia.org/wiki/Continuation-passing_style" target="_blank" rel="external">CPS</a>就是一种编程风格。我们调用一个函数后，一般要拿到函数的返回值后再去做各种处理，而cps则将后续的处理逻辑（封装成一个函数）直接传给被调用的函数。有一些“控制反转（IoC）”的味道在里面。</p>
<p>这么说太抽象，还是看例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// ES6语法</span></div><div class="line"><span class="comment">// 一个普通版本的计算阶乘函数，很简单的递归</span></div><div class="line"><span class="keyword">const</span> fac = n =&gt; {</div><div class="line">  <span class="comment">// 递归最重要的是啥？终止条件啊，没有终止条件信不信直接stackoverflow给你看</span></div><div class="line">  <span class="keyword">if</span> (n == <span class="number">0</span>) {</div><div class="line">    <span class="keyword">return</span> <span class="number">1</span>;</div><div class="line">  }</div><div class="line">  <span class="keyword">else</span> {</div><div class="line">    <span class="comment">// 为啥会stackoverflow嘞，因为这里必须要将n压栈,等fac(n - 1)计算完毕后，n出栈，再做乘法</span></div><div class="line">    <span class="comment">// 要保留当时的“作案现场”，这也是函数调用后返回的基本操作</span></div><div class="line">    <span class="comment">// 如果n足够大，就算有终止条件，这里也会直接栈溢出</span></div><div class="line">    <span class="keyword">return</span> n * fac(n - <span class="number">1</span>);</div><div class="line">  }</div><div class="line">};</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log(`<span class="number">10</span>的阶乘是${fac(<span class="number">10</span>)}`)</div></pre></td></tr></table></figure>

<p>上面就是一个非常简单的计算阶乘的函数了，但这种递归非常容易出问题。还记得递归的经典反面教材斐波那契数列么？如果用递归法去求斐波那契数列，非常容易StackOverflow。所以各种算法教材中常常强调“迭代优于递归”，我在刷leetcode时用到递归也总是非常小心，因为心里没底。。。</p>
<p>如果将上面的函数改写成cps形式，就会变成下面这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// cps版本的计算阶乘</span></div><div class="line"><span class="comment">// 注意这里面不会再有return语句了</span></div><div class="line"><span class="comment">// 正常情况下应该是计算出阶乘后返回结果, 让调用方决定怎么去使用结果</span></div><div class="line"><span class="comment">// 但cps风格中, 如何处理结果已经以callback的形式传过来了</span></div><div class="line"><span class="comment">// 计算出n的阶乘后,直接callback(n)就可以了, 所以不用return</span></div><div class="line"><span class="keyword">const</span> facWithCps = (n, callback) =&gt; {</div><div class="line">  <span class="keyword">if</span> (n == <span class="number">0</span>) {</div><div class="line">    callback(<span class="number">1</span>);</div><div class="line">  }</div><div class="line">  <span class="keyword">else</span> {</div><div class="line">    <span class="comment">// 这里要注意下, ret是n-1的阶乘, 而callback要的是n的阶乘, 所以callback(n * ret)</span></div><div class="line">    <span class="comment">// 相当于又传了一个新的匿名函数进去</span></div><div class="line">    facWithCps(n - <span class="number">1</span>, ret =&gt; callback(n * ret));</div><div class="line">  }</div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">// 计算出n的阶乘后，要对结果做怎样的处理？这个就是callback决定的事</span></div><div class="line"><span class="comment">// callback最终只会调用一次, 参数是1*2*3*4...*n</span></div><div class="line"><span class="keyword">const</span> callback = n =&gt; {</div><div class="line">  <span class="comment">// 由于只是个例子，所以没对结果做什么处理，直接输出了</span></div><div class="line">  <span class="built_in">console</span>.log(`结果是${n}`)</div><div class="line">};</div><div class="line"></div><div class="line">facWithCps(<span class="number">10</span>, callback);</div></pre></td></tr></table></figure>

<p>计算结果和普通的递归是一样的，但却难懂了很多。这么写的好处在哪里？细心观察就知道这段程序理论上不需要压栈/出栈，在函数的最后一行，直接开始下一次递归就可以了，不需要保存上一次递归的状态，这种递归被称作<a href="https://zh.wikipedia.org/wiki/%E5%B0%BE%E8%B0%83%E7%94%A8" target="_blank" rel="external">尾递归</a>（尾调用的一种特殊情况）。</p>
<blockquote>形式上只要是最后一个return语句返回的是一个完整函数，它就是尾递归。</blockquote>

<p>理论上来说，这段程序即使没有终止条件，无限递归下去，也不会StackOverflow。</p>
<p>编译器通常可以对尾递归做很多优化，甚至可以将尾递归消除。这种优化在ES中被称作<code>proper tail calls</code>，是ES6中的标准，但目前尚没有主流的浏览器实现这种优化，见<a href="http://kangax.github.io/compat-table/es6/" target="_blank" rel="external">这里</a>。</p>
<p>cps虽然形式更复杂了，但表达能力也更强，可以基于这个去封装特定用途函数，因为callback的逻辑是由外部控制的。抽象点说，就是用一个高阶函数去生成普通函数。</p>
<p>cps在函数式编程中似乎是非常重要的一个东西。因为很多函数式语言都非常符号化，非常学院派，不提供if/for/while之类的流程控制语句，而这些都可以用cps去实现。所以对函数式语言来说，尾递归优化是标配。</p>
<p><a href="https://www.zhihu.com/question/20822815" target="_blank" rel="external">王垠的40行代码</a>据说就是能自动对已有的函数做cps变换？不懂。。。而且似乎还涉及到很多数学上的东西，我一介学渣只能仰望。。。</p>
<p>研究cps的过程中，突然又想到<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures" target="_blank" rel="external">闭包</a>。这也是我一直很模糊的一个概念，每次看定义/示例都觉得已经懂了，但过一段时间之后又觉得自己什么都没懂。。。<br>通俗点说就是每个函数可以有自己的私有变量？或者叫做自己的状态，外界无法修改。感觉上和OOP中的成员变量很像么。毕竟js中没有private/public之类的变量作用域。但却可以用闭包模拟。<br>但这种东西呢，感觉上更像是种hack，似乎还容易造成内存泄漏，少用为妙。<br>一个还可以的闭包教程：<a href="http://www.cnblogs.com/frankfang/archive/2011/08/03/2125663.html" target="_blank" rel="external">http://www.cnblogs.com/frankfang/archive/2011/08/03/2125663.html</a></p>
<p>一些参考资料：<br>知乎上的一些讨论帖：<a href="https://www.zhihu.com/question/24453254" target="_blank" rel="external">1</a>、<a href="https://www.zhihu.com/question/27581940" target="_blank" rel="external">2</a>、<a href="https://www.zhihu.com/question/20259086" target="_blank" rel="external">3</a><br>其他一些讨论帖：<a href="http://www.cricode.com/613.html" target="_blank" rel="external">1</a>、<a href="https://www.v2ex.com/t/61836" target="_blank" rel="external">2</a><br><a href="https://github.com/vczh/tinymoe" target="_blank" rel="external">轮子哥的tinymoe语言</a>，感觉拿来当编译原理的入门不错<br><a href="http://blog.zhaojie.me/2009/03/tail-recursion-and-continuation.html" target="_blank" rel="external">尾递归与Continuation</a></p>
<h1 id="async/await">async/await</h1>
<p>这是个很好玩的东西。但要搞明白这个玩意还要从js的异步机制说起。</p>
<p>对于刚接触js语言的人来说，印象最深刻的是什么？至少对我而言，是各种callback。这也算是js的一大特色（是不是也可以说是函数式语言的特色）。<br>如果在java中，我要实现一个“异步取数据并消费”的逻辑，我会怎么做？很大概率是写一个生产者-消费者模型，搞一个BlockingQueue，“多线程”在java中是一个很自然的概念（HotSpotVM会把java线程映射到系统线程）。或者搞个事件驱动，比如经典的EventBus。如果强行用回调实现反而麻烦，必须要定义一个接口，回调还要以inner anonymous class的形式传过来，这种代码写起来/看起来都很别扭。当初用到zookeeper的<a href="http://zookeeper.apache.org/doc/r3.4.9/api/index.html" target="_blank" rel="external">API</a>，它提供了同步/异步两种风格的接口，异步版本用起来就很是蛋疼。。。<br>如果是在js中，就没得选择了，只能使用callback。因为js的执行是单线程的（当然这是特指浏览器和node，不排除以后某种js的解释器可能用多线程执行，不过难度太大了）。对于单线程的js而言，要想实现并发的效果，就只能依赖于各种异步操作的callback，具体可以去google：<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop" target="_blank" rel="external">Event Loop</a>。好在js的callback用起来很方便（跟java比），因为js是弱类型而且function是一等公民，有了箭头函数之后就更简洁了。</p>
<p>话说，之前不是总有人纠结基于event loop的并发和基于线程/锁的并发哪个效率高么，还搞各种吞吐率测试。应该说最传统的提高并发量的手段就是多线程，突然出现一个单进程单线程的node确实很容易被人质疑。并发模型其实还有<a href="http://www.ituring.com.cn/book/1649" target="_blank" rel="external">很多种</a>的，各有优劣。还有Concurrent vs Parallel的区别，并发也是个大坑啊。。。</p>
<p>再话说，js是单线程的，但并不意味着浏览器是单线程，只是说浏览器中有单独一个线程负责执行js而已。很常见的一个现象：浏览器加载外部资源（图片、css、js）时，是可以并发的；包括我们在js中如果同时发送多个ajax，其实也是并发的。并发的请求数是由<a href="http://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser" target="_blank" rel="external">浏览器</a>限制的，chrome中有一个参数可以改这个值，具体是那个参数忘了。。。<br>这又引申出一个问题，如果多个ajax并发，哪个回调会先被执行？个人猜测是跟请求返回的顺序一致的，哪个请求先返回就先放到事件队列，回调先被执行，跟请求发起的顺序无关。</p>
<p>js语言的设计思路导致它只能用callback解决并发的问题。本来也没啥事，但程序复杂了之后，代码一多，大家渐渐发现回调太多了，就是所谓的callback hell。callback本身就是反直觉的，很容易造成程序的结构复杂，难以维护。更蛋疼的是各种回调的嵌套。一个很常见的场景：后端提供了CRUD的接口，我要先ajax请求某个接口，按一定条件查询，得到某条记录的id，再拿这个id去查询另一个接口得到最终的结果，类似下面这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 第一次ajax，查询出id</span></div><div class="line">$.get(<span class="string">'http://xxx/user?name=jxy'</span>, <span class="function"><span class="keyword">function</span><span class="params">(data)</span></span>{</div><div class="line">  <span class="keyword">var</span> id = data.id;</div><div class="line">  <span class="comment">// 第二次ajax，根据id查询出需要的数据</span></div><div class="line">  $.get(<span class="string">'http://xxx/another?id='</span>+id, <span class="function"><span class="keyword">function</span><span class="params">(data)</span></span>{</div><div class="line">    <span class="comment">// 这里才是真正的处理逻辑</span></div><div class="line">    <span class="comment">// do something...  </span></div><div class="line">  });</div><div class="line">});</div></pre></td></tr></table></figure>

<p>这样的代码，写起来很蛋疼，支离破碎，而且可能有更多层的嵌套，维护起来十分麻烦。看着满屏幕的<code>function</code>，很容易晕。。。于是有聪明人提出了<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" rel="external">Promise</a>模式。这个东西其实也挺古老了，我最开始听说promise是在<a href="https://api.jquery.com/promise/" target="_blank" rel="external">jquery</a>中，还有<a href="https://api.jquery.com/category/deferred-object/" target="_blank" rel="external">deferred对象</a>。在ES6中，promise正式成为一项标准，不过和jquery的promise用法不太一样。如果用promise去重写上面那段代码，大概是这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 假设$.get会返回一个promise对象</span></div><div class="line">$.get(<span class="string">'http://xxx/user?name=jxy'</span>).then(</div><div class="line">  <span class="function"><span class="keyword">function</span><span class="params">(data)</span></span>{</div><div class="line">    <span class="keyword">var</span> id = data.id;</div><div class="line">    <span class="keyword">return</span> $.get(<span class="string">'http://xxx/another?id='</span>+id)；</div><div class="line">  }</div><div class="line">).then(</div><div class="line">  <span class="function"><span class="keyword">function</span><span class="params">(data)</span></span>{</div><div class="line">    <span class="comment">// 这里才是真正的处理逻辑</span></div><div class="line">    <span class="comment">// do something... </span></div><div class="line">  }</div><div class="line">);</div></pre></td></tr></table></figure>

<p>由于<code>then()</code>可以链式调用，整个流程清晰了很多，不需要嵌套了，但回调函数还是无法避免。promise只是把传统的嵌套回调变成了链式调用，或者叫<a href="https://en.wikipedia.org/wiki/Fluent_interface" target="_blank" rel="external">fluent style</a>。<br>关于promise的更多资料可以参考<a href="http://wiki.jikexueyuan.com/project/es6/promise.html" target="_blank" rel="external">这个</a>和<a href="https://www.sitepoint.com/six-things-might-know-promises/" target="_blank" rel="external">这个</a>，其实还有很多高级的用法。</p>
<p>为了解决promise的的问题，于是有更聪明的人提出了async/await，这被称作js中异步调用的终极解决方案。async/await特性本来想随ES7一起发布的，但没赶上截止日。。。目前好像是stage-3的状态，已经很接近发布了。最终发布的ES7只包含很少的<a href="http://www.2ality.com/2016/01/ecmascript-2016.html" target="_blank" rel="external">新特性</a>，async/await估计要随ES8一起发布了，不过现在可以通过<a href="http://babeljs.io/" target="_blank" rel="external">babel</a>使用。<br>话说，ES的这种发布模式我觉得挺好的，看看java9都<a href="http://www.infoq.com/cn/news/2016/12/java9-latest-schedule-at-risk" target="_blank" rel="external">难产</a>多久了。。。<br>如果用async/await来写上面那段代码，大概是这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 伪代码</span></div><div class="line"><span class="comment">// 用async修饰一个函数</span></div><div class="line">async <span class="function"><span class="keyword">function</span> <span class="title">getData</span><span class="params">()</span></span>{</div><div class="line">  <span class="comment">// 用await标记异步操作，会自动等待异步操作执行完毕之后再继续向下执行</span></div><div class="line">  <span class="keyword">const</span> user = await $.get(<span class="string">'http://xxx/user?name=jxy'</span>);</div><div class="line">  <span class="keyword">const</span> id = user.id;</div><div class="line">  <span class="keyword">const</span> data = await $.get(<span class="string">'http://xxx/another?id='</span>+id);</div><div class="line">  <span class="comment">// 真正处理data</span></div><div class="line">  <span class="comment">// do something...</span></div><div class="line">};</div><div class="line"></div><div class="line">getData();</div></pre></td></tr></table></figure>

<p>可以看出，这个代码的逻辑是非常流畅、非常线性的，完全摒弃了回调的存在，写起来非常舒服，读起来也容易懂，“像写同步调用一样写异步调用”。只是有一些需要注意的地方：</p>
<ul>
<li>async必须和await搭配使用，await只能用在async函数内部。async的语义是“这个函数内部有异步操作”。</li>
<li>await后面的变量，必须是一个promise对象。await的语义是“后面的语句是一个异步操作，先不要继续向下执行，等后面这个promise状态变为resolved后再继续”。</li>
<li>如果异步操作出错，只能通过try-catch来捕捉错误并处理，不像promise对象一样可以用catch()方法。对于js程序员来说这可能有点啰嗦，不过对于java程序员来说倒是很亲切。</li>
</ul>
<p>async/await还是挺好用的，要不怎么号称是“终极解决方案”。不过我比较怀疑，前端的同学们这么能折腾，会不会哪天又搞出一个“超・终极解决方案”。。。</p>
<p>其实，async/await只是一个语法糖，它背后的原理是ES6的另一个新特性：生成器。babel支持async/await的原理，其实也是转换为生成器的写法，见<a href="http://babeljs.io/docs/plugins/transform-async-to-generator/" target="_blank" rel="external">这个插件</a>。</p>
<h1 id="生成器">生成器</h1>
<p>生成器（Generators）也是个很好玩的东西，是随ES6发布的新特性，一些基本的使用可以参考<a href="http://www.infoq.com/cn/articles/es6-in-depth-generators" target="_blank" rel="external">InfoQ的教程</a>。</p>
<p>个人感觉，生成器的本质，其实就是ES6对<a href="https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B" target="_blank" rel="external">协程</a>特性的实现，而且从python中借鉴了很多理念。但js/python的协程，貌似和lua/erlang/go之类的协程还不太一样，本来不是为了并发设计的，而是为了方便的迭代，所以才叫做“生成器”，一个例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 是不是和python里面的range/xrange函数很像</span></div><div class="line"><span class="function"><span class="keyword">function</span>* <span class="title">range</span><span class="params">(start, stop)</span> </span>{</div><div class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = start; i &lt; stop; i++)</div><div class="line">    <span class="keyword">yield</span> i;</div><div class="line">}</div><div class="line"><span class="comment">// 生成器配合for-of语法非常好用</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i of range(<span class="number">10</span>, <span class="number">20</span>)) {</div><div class="line">  <span class="built_in">console</span>.log(i);</div><div class="line">}</div></pre></td></tr></table></figure>

<p>所以，将“生成器”当作“协程”来用，是一种无心插柳么。。。不过他们的逻辑本来就很相似，就算语言原生提供了协程（比如lua），其实也可以包装下当作“生成器”来用。</p>
<p>那么，啥是协程？几乎所有的语言中，都会有“函数”，或者叫做过程/例程/方法/子程序（function/procedure/routine/method/sub-program），反正就是这么个东西。执行一个函数时，都要保存上下文（压栈），函数执行完毕（碰到return语句或执行到最后）再恢复上下文（出栈）。在这个过程中，函数只有一个入口点和一个出口点，一个函数必须从头开始执行，而且开始后，你必须等待它执行完毕。换句话说，一旦开始执行，这个函数会一直掌握着代码的执行权。</p>
<p>而协程(co-routine)，跟函数非常类似，区别在于它可以有多个入口和出口。在协程执行的过程中，你可以在某行代码处打断协程，跑去干别的事，然后再回来继续执行。一般通过yield关键字实现，比如js/python。但也有不是用关键字，而是用特定方法的，比如lua的coroutine.yield()。yield关键字的语义就是“将代码的执行权交给其他人”。从某种意义上上来说，yield关键字有点像goto语句，都会很“强硬”的直接改变代码执行流程。</p>
<p>这个“打断-恢复”就是协程最关键的特性，也是它能被用于处理并发问题的关键。这个过程也是需要切换上下文的，协程从yield的地方继续执行时，必须恢复当初中断时的状态，但未必是像函数一样用栈去实现了，跟具体语言或者协程库的实现有关。相对进程/线程的切换而言，协程的上下文切换代价一般比较低，需要的内存会少很多。<br>更神奇的是，协程的状态可以被外部“干涉”，可以和外部交换数据，一个例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 一个js的例子，网上随便找的，其他支持协程的语言也有类似的用法</span></div><div class="line"><span class="function"><span class="keyword">function</span> *<span class="title">foo</span><span class="params">(x)</span> </span>{</div><div class="line">    <span class="keyword">var</span> y = <span class="number">2</span> * (<span class="keyword">yield</span> (x + <span class="number">1</span>));</div><div class="line">    <span class="keyword">var</span> z = <span class="keyword">yield</span> (y / <span class="number">3</span>);</div><div class="line">    <span class="keyword">return</span> (x + y + z);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">var</span> it = foo( <span class="number">5</span> );</div><div class="line"></div><div class="line"><span class="built_in">console</span>.log( it.next() );       <span class="comment">// { value:6, done:false }</span></div><div class="line"><span class="comment">// 传入12，所以yied语句返回值12，所以y=24</span></div><div class="line"><span class="built_in">console</span>.log( it.next( <span class="number">12</span> ) );   <span class="comment">// { value:8, done:false }</span></div><div class="line"><span class="comment">// x=5, y=24, z=13</span></div><div class="line"><span class="built_in">console</span>.log( it.next( <span class="number">13</span> ) );   <span class="comment">// { value:42, done:true }</span></div></pre></td></tr></table></figure>

<p>协程的另一个关键特性就是协程之间的切换都是在同一个线程中发生的。还记得《操作系统原理》中的内核线程和用户线程么？内核线程的调度完全由系统内核负责，用户端只负责执行就可以；用户线程则需要单独提供调度器，系统内核完全不知道线程的存在。协程就有那么点用户线程的意思，用户要自己负责协程之间的调度。</p>
<p>对于js而言，因为它是单线程的，所以协程对它而言“永不并发”。同一时刻只可能有一个协程的代码在运行，最多是代码的执行权在不同协程间切换而已。</p>
<p>协程的调度算法似乎也是个坑，没啥统一的规范。。。通俗点说，我在协程中yield交出执行权后，这个执行权交给谁？是我直接决定交给另外的某个协程（symmetric coroutines，对称协程，调度是平级的）？还是交给我的上层，让上层去决定接下来如何执行（asymmetric coroutines，非对称协程）？这就是调度算法要决定的事情。<br>就js/python而言，他们的协程都是非对称的，yield时执行权都会返回给上层。</p>
<p>由于协程的调度是在用户端实现的，所以完全可以根据需要写一个自己的调度器，一个<a href="http://www.jianshu.com/p/8ec105854e05" target="_blank" rel="external">例子</a>。</p>
<p>扯了一大堆协程的东西，好像有点偏离主题。。。本来是在说js的生成器的。。。<br>生成器/协程除了用在迭代中，还可以用来简化js的异步编程，换句话说，解决callback hell。其实思路也很简单：我要执行异步请求的时候（比如ajax），就yield一下，把执行权交出去，让js引擎先去执行其他的代码；异步请求结束后，再把执行权要回来，从yield的地方继续执行。所以最关键的问题是：执行权的交换是如何做到的？有点类似调度器了。目前看来一般有2种方式：</p>
<h2 id="Thunk函数">Thunk函数</h2>
<p><a href="https://en.wikipedia.org/wiki/Thunk" target="_blank" rel="external">Thunk函数</a>是一个很古老的概念了，从函数式编程中发展而来的，似乎最初是为了惰性求值，参考<a href="https://www.techopedia.com/definition/2818/thunk" target="_blank" rel="external">这个</a>。但现在Thunk函数的概念已经很宽泛了，似乎很多辅助性的函数都可以被称作Thunk函数（thunk函数一般都是通过代码自动生成的）。</p>
<p>个人理解，对js的生成器而言，Thunk函数就是“接受一个callback为参数，做一些操作，并在最后执行callback”的函数。例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">const</span> getData = (callback) =&gt; {</div><div class="line">  <span class="comment">// 模拟异步操作</span></div><div class="line">  setTimeout(() =&gt; {</div><div class="line">    <span class="built_in">console</span>.log(<span class="string">'异步操作结束'</span>);</div><div class="line">    <span class="comment">// 异步操作结束后执行callback</span></div><div class="line">    <span class="comment">// callback也可以带参数, 不过这个例子里没有</span></div><div class="line">    callback();</div><div class="line">  }, <span class="number">2000</span>);</div><div class="line">};</div></pre></td></tr></table></figure>



<p>是不是和之前的cps变换中Continuation的概念很像？其实很多概念都是相通的。</p>
<p>Thunk函数能做什么？由于Thunk函数的最后会调用callback，如果yield（给出执行权）时返回一个Thunk函数，就可以利用callback在Thunk执行完毕后把执行权再“要回来”。一个例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 假设我们要做一个聊天机器人，机器人说每句话时都必须请求一次服务端</span></div><div class="line"></div><div class="line"><span class="comment">// 为了简单，用一个函数去生成Thunk函数，即是所谓的高阶函数</span></div><div class="line"><span class="keyword">const</span> Thunk = (words) =&gt; {</div><div class="line">  <span class="keyword">return</span> (callback) =&gt; {</div><div class="line">    <span class="comment">// 随机延迟一段时间，用于模拟请求服务端</span></div><div class="line">    <span class="keyword">const</span> rand = <span class="built_in">Math</span>.random() * <span class="number">5000</span>;</div><div class="line">    setTimeout(() =&gt; {</div><div class="line">      <span class="comment">// 如果执行到这里，说明异步请求已经返回了</span></div><div class="line">      <span class="built_in">console</span>.log(words);</div><div class="line">      <span class="built_in">console</span>.log(`本句耗时 ${rand} ms`);</div><div class="line">      <span class="comment">// 服务端返回的数据是啥？大多数业务场景下，我们都要对这个数据做进一步处理</span></div><div class="line">      <span class="comment">// 所以要将返回的结果传给callback，这样在生成器内部才能继续处理</span></div><div class="line">      <span class="comment">// 为了简单我直接把rand传过去了</span></div><div class="line">      callback(rand);</div><div class="line">    }, rand);</div><div class="line">  }</div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">// 定义机器人，这个generator的执行分为3步</span></div><div class="line"><span class="comment">// 每执行一步,都会返回一个Thunk函数,然后将控制权交出去</span></div><div class="line"><span class="function"><span class="keyword">function</span>* <span class="title">robot</span><span class="params">()</span> </span>{</div><div class="line">  <span class="keyword">const</span> a = <span class="keyword">yield</span> Thunk(<span class="string">'你好'</span>);</div><div class="line">  <span class="keyword">const</span> b = <span class="keyword">yield</span> Thunk(<span class="string">'我是robot'</span>);</div><div class="line">  <span class="keyword">const</span> c = <span class="keyword">yield</span> Thunk(<span class="string">'今天天气不错'</span>);</div><div class="line">  <span class="built_in">console</span>.log(`总共耗时${a + b + c}ms`);</div><div class="line">}</div></pre></td></tr></table></figure>



<p>如上，我们定义好生成器了，如何执行呢？所以还需要一个程序去调度它，或者叫做“驱动程序”：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 定义一个run函数，这个函数会自动执行生成器</span></div><div class="line"><span class="keyword">const</span> run = (generatorName) =&gt; {</div><div class="line">  <span class="comment">// 生成器对象</span></div><div class="line">  <span class="keyword">const</span> generator = generatorName();</div><div class="line">  <span class="comment">// 要传给thunk函数的callback</span></div><div class="line">  <span class="keyword">const</span> callback = (input) =&gt; {</div><div class="line">    <span class="keyword">const</span> result = generator.next(input);  <span class="comment">// 执行下一步，注意把input传进去作为yield语句的值</span></div><div class="line">    <span class="keyword">if</span> (result.done) {  <span class="comment">// 如果已经执行到最后一步, 就停止</span></div><div class="line">      <span class="keyword">return</span>;</div><div class="line">    } <span class="keyword">else</span> {</div><div class="line">      result.value(callback);  <span class="comment">// 返回的value是一个Thunk函数, Thunk函数可以接收一个callback继续执行, 而callback的执行又会继续触发generator.next()</span></div><div class="line"></div><div class="line">      <span class="comment">// 本质上还是递归, 而且是尾递归</span></div><div class="line">      <span class="comment">// 这种东西是可能stackoverflow的吧, 尤其是现在浏览器都没有优化过</span></div><div class="line">    }</div><div class="line">  };</div><div class="line"></div><div class="line">  <span class="comment">// 这里其实有点闭包的意味了，callback函数有一个私有的“成员变量”generator，每次执行callback时状态都不一样</span></div><div class="line">  <span class="comment">// 如果最后return callback，就真的是闭包了</span></div><div class="line"></div><div class="line">  <span class="comment">// 第一次执行时不用传参数，传了也会被忽略</span></div><div class="line">  callback();</div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">// 开始执行robot生成器</span></div><div class="line">run(robot);</div></pre></td></tr></table></figure>

<p>打开console即可看到效果。说实话这段代码挺难懂的。。。估计过一段时间我自己都不懂了。。。关键是要理解“代码执行权”的交换过程。但一旦看懂就会觉得很神奇，“卧槽这也可以”的感觉。</p>
<p>有了这个run函数，我们就可以“像写同步调用一样写异步调用了”。看下那个robot的生成器，完全屏蔽了异步调用的复杂性，也不用再跟回调打交道了。当然这个thunk+run的机制，可以做的更复杂、更通用一些，比如加上错误处理之类的。其实有很多人已经做好了类似的库，用的最多的就是<a href="https://github.com/tj/node-thunkify" target="_blank" rel="external">thunkify</a>+<a href="https://github.com/tj/co" target="_blank" rel="external">co</a>，不过这两个库是用于node的，不知道能不能用在浏览器端。</p>
<p>仔细观察下，这个<code>function* + yield</code>的语法就跟<code>async/await</code>很像了。</p>
<h2 id="Promise对象">Promise对象</h2>
<p>用生成器处理异步请求的另一个方法就是Promise对象了。其实核心原理还是一样的，就是“异步操作结束后代码执行权的交换”。只不过Thunk函数是用执行完毕之后的callback实现的，而Promise是用自带then()方法，毕竟如果then()方法被触发了，就说明异步请求已经结束了。而且Promise是ES自带的对象，会更通用一点。</p>
<p>如果用Promise对象改写上面的那个机器人的例子，大概是这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 返回一个promise对象</span></div><div class="line"><span class="keyword">const</span> say = (words) =&gt; {</div><div class="line">  <span class="comment">// 省事起见，忽略reject参数</span></div><div class="line">  <span class="keyword">return</span> <span class="keyword">new</span> Promise((resolve) =&gt; {</div><div class="line">    <span class="keyword">const</span> rand = <span class="built_in">Math</span>.random() * <span class="number">5000</span>;</div><div class="line">    setTimeout(() =&gt; {</div><div class="line">      <span class="built_in">console</span>.log(words);</div><div class="line">      <span class="built_in">console</span>.log(`本句耗时 ${rand} ms`);</div><div class="line">      resolve(rand);</div><div class="line">    }, rand);</div><div class="line">  });</div><div class="line">};</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span>* <span class="title">robot</span><span class="params">()</span> </span>{</div><div class="line">  <span class="keyword">const</span> a = <span class="keyword">yield</span> say(<span class="string">'你好'</span>);</div><div class="line">  <span class="keyword">const</span> b = <span class="keyword">yield</span> say(<span class="string">'我是robot'</span>);</div><div class="line">  <span class="keyword">const</span> c = <span class="keyword">yield</span> say(<span class="string">'今天天气不错'</span>);</div><div class="line">  <span class="built_in">console</span>.log(`总共耗时${a + b + c}ms`);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">const</span> run = (generatorName) =&gt; {</div><div class="line">  <span class="keyword">const</span> generator = generatorName();</div><div class="line"></div><div class="line">  <span class="keyword">const</span> callback = (input) =&gt; {</div><div class="line">    <span class="keyword">const</span> result = generator.next(input);</div><div class="line">    <span class="keyword">if</span> (result.done) {</div><div class="line">      <span class="keyword">return</span>;</div><div class="line">    } <span class="keyword">else</span> {</div><div class="line">      <span class="comment">// 如果更严谨点，其实这里应该判断下result.value确实是promise对象</span></div><div class="line">      result.value.then(callback);</div><div class="line">    }</div><div class="line">  };</div><div class="line"></div><div class="line">  callback();</div><div class="line">};</div><div class="line"></div><div class="line">run(robot);</div></pre></td></tr></table></figure>

<p>基本原理和Thunk是一样的，不再赘述。<br>这种情况下，<code>function* + yield</code>就和<code>async + await</code>完全等价了。所以说async和await只是生成器+promise的一个语法糖，好处在于更规范，毕竟是从语法层面上去支持。生成器+promise总是有点奇技淫巧的感觉。。。</p>
<h2 id="其他">其他</h2>
<p>生成器其实还有很多高级的用法，比如<code>yield*</code>。</p>
<p>很遗憾的一个地方是生成器不能配合箭头函数使用，见<a href="http://stackoverflow.com/questions/27661306/can-i-use-es6s-arrow-function-syntax-with-generators-arrow-notation" target="_blank" rel="external">这里</a>。箭头函数和普通函数还是有很多不同的，哪里用箭头函数，哪里用普通函数，似乎还有很多争议。<br>箭头函数的一大好处就是自动绑定this，否则就只能手动绑定或使用<code>const that=this</code>之类的hack。。。<br>但async/await却可以配合箭头函数使用，真是奇怪。。。</p>
<h2 id="参考资料">参考资料</h2>
<p><a href="http://coolshell.cn/articles/10975.html" target="_blank" rel="external">http://coolshell.cn/articles/10975.html</a><br><a href="https://www.zhihu.com/question/20511233" target="_blank" rel="external">https://www.zhihu.com/question/20511233</a><br><a href="https://gold.xitu.io/entry/581d9f8cda2f60005df771fe" target="_blank" rel="external">https://gold.xitu.io/entry/581d9f8cda2f60005df771fe</a><br><a href="http://stackoverflow.com/questions/2641489/what-is-a-thunk" target="_blank" rel="external">http://stackoverflow.com/questions/2641489/what-is-a-thunk</a><br><a href="http://web.jobbole.com/85901/" target="_blank" rel="external">http://web.jobbole.com/85901/</a><br><a href="http://www.cnblogs.com/Mainz/p/3552717.html" target="_blank" rel="external">Javascript是单线程的深入分析</a></p>
<h1 id="redux是个好东西">redux是个好东西</h1>
<p>对redux也是久闻大名了。<a href="/2016/09/06/zhen-second-hand-qianduan/">之前</a>我就说过react组件之间要共享状态，就只能写一些很恶心的代码，结果这居然是react<a href="https://facebook.github.io/react/docs/lifting-state-up.html" target="_blank" rel="external">官方推荐的做法</a>。。。但我还是觉得很蛋疼。<br>那时我虽然没用过redux，但知道他要解决的是什么问题。不过实际研究了下redux后，发现实现上和我想的还是有些不一样。</p>
<p>这不是个redux教程，只是总结一些感想。</p>
<p>借用一张图片：<br><img src="/2017/01/15/2017-and-redux/1.jpg" alt=""></p>
<p>其实redux的核心非常简单，概念上无非是store/reducer/action，操作上也就是dispatch/subscribe，都很好理解。一些tips：</p>
<ul>
<li>reducer必须是个纯函数。纯函数是非常非常重要的概念，action creator也必须是纯函数。纯=没有副作用/没有状态/对同样的输入肯定会得到同样的输出。</li>
<li><code>reducer(previousState, action) =&gt; newState</code>，这就是reducer的作用，每次都应该返回一个新的对象。为什么这个函数叫做 Reducer呢？因为它可以作为数组的reduce方法的参数。MapReduce中的reduce也正是来源于此。也许以后可以批量处理action？感觉不太可能。。。</li>
<li>Store是全局唯一的状态存储，只能通过dispatch去修改，对应用而言store是只读的</li>
<li>状态变化时会触发listener，但不会传什么参数，需要自己去查store</li>
<li>reducer在 default 情况下一定返回旧的 state。千万不要返回undefined。。。血泪教训。</li>
<li>注意全局的初始状态</li>
<li>action应该是个不可变对象。尽量减少在action中传递的数据。</li>
<li><code>Object.assign()</code>和<code>{...state,}</code>应该是同样的效果。但要注意深拷贝和浅拷贝的区别。<code>Object.assign()</code>貌似是浅拷贝，有些时候会有潜在的bug。</li>
<li><code>bindActionCreators</code>/<code>combineReducers</code>有多种使用方式，根据传入参数的类型不同（object/function）区分。当然结果上没啥区别，爱用哪种看心情</li>
</ul>
<p>另外注意redux其实跟react没啥关系，可以应用到其他任意地方，因为它本身只是一个“可预测的状态容器”。我曾经见过把redux用到微信小程序上的。</p>
<p>redux的设计借鉴了很多函数式编程的思想，非常抽象，非常学院派，概念简单，功能却很强大。而且它的实现里大量利用了js的函数式特性，在我看来可以做到很多“黑魔法”一样的事情。如果看了它的源码，就会叹为观止，我都怀疑自己会不会js了，我可能是学了假的js。。。比如<code>bindActionCreators</code>/<code>combineReducers</code>之类，只会用，凭空想很难想到它是如何实现的，看了代码之后都觉得实现非常精妙。而且redux的<a href="https://github.com/reactjs/redux/tree/master/src" target="_blank" rel="external">代码量非常少</a>，建议大家都看看。<br>如果java的思想去做类似的事，有些是根本实现不了，有些就算实现了代码也会很别扭。。。<br>也许这就是函数式语言的特性吧，很适合开脑洞，能做出很多精巧、让人拍案叫绝的东西。</p>
<p>另外，对于<a href="https://github.com/reactjs/react-redux" target="_blank" rel="external">react-redux</a>而言，它使用了一种被称作“<a href="https://facebook.github.io/react/docs/higher-order-components.html" target="_blank" rel="external">高阶组件</a>”（Higher-Order Component）的技巧，使用<code>connect()</code>方法包装原有的组件使状态被redux托管。这种技巧还是很有用的，antd的<a href="https://ant.design/components/form-cn/" target="_blank" rel="external">Form组件</a>中也有用到。</p>
<p>redux的一个“缺点”就是让你的程序更复杂，更难懂。更复杂的程序就意味着更容易有bug。如果不了解redux的原理，也很容易搞出问题来。“如果你不确定是否需要redux，那就不要用”。千万不要为了符合所谓的“业界标准”而引入redux，要看实际的需求。</p>
<p>但我对redux还是有一些遗留的问题：</p>
<ul>
<li>为什么reducer不会跟特定action关联</li>
</ul>
<p>我最开始以为redux是一个类似于EventBus的publish-subscribe模型。每个reducer只订阅特定类型的action。换句话说，每个action只会触发一个reducer的代码。<br>但redux目前的实现却是触发一个action后，这个action要在所有的reducer中都“过一遍”，因为redux是不知道哪种reducer处理哪种类型的action的，也就是没有一个“注册”的过程。<br>我们在写reducer时会拆分成一个个小的reducer，每个小reducer都只处理特定action，最后用combineReducers合并为一个大的reducer。但这也只是为了编码上的方便而已，处理action时没啥本质区别。</p>
<p>为啥redux会采用这种方式呢？也许是因为比较简单吧，但如果action的种类太多，真的不会有性能问题么？这种方式还有个问题，如果某两个reducer会处理同样type的action，可能会隐性的bug，而且不好排查。只能在编码时注意。</p>
<p>理论上来说publish-subscribe应该效率会更高一点，因为有着“<a href="https://zh.wikipedia.org/wiki/%E5%85%88%E9%A9%97" target="_blank" rel="external">先验知识</a>”，可以针对性的处理。但也要考虑到<a href="https://github.com/google/guava/wiki/EventBusExplained#what-eventbus-problems-may-only-be-detected-later-at-runtime" target="_blank" rel="external">DeadEvent</a>的问题。</p>
<ul>
<li>newState会有一个merge的过程么？</li>
</ul>
<p>store中保存着preState，当有action触发时，会经过reducer得到一个newState，newState和preState会有一个merge的过程么？还是直接丢掉preState？</p>
<p>现在redux的实现是直接丢掉preState。这样如果action非常多，就会有非常多的无用对象，会不会有gc问题？<br>所有用到不可变对象的地方，其实都可能有类似的问题。</p>
<ul>
<li>如何设计状态是个大麻烦</li>
</ul>
<p>虽然state本身只是一个普通的js对象，理论上可以有任意复杂的结构，但为了方便使用和维护还是要遵循一些原则。个人感觉应该尽量“扁平”，尽量不要嵌套。<br>一些文档中提到的设计原则：尽量使state可以轻松的转化为JSON；尽可能地把state范式化，不存在嵌套；把所有数据放到一个对象里，每个数据以ID为主键；把state想象成一个数据库。<br>另外，react组件有自己的状态，但store中又存在全局状态。哪些状态做成全局的，哪些做成局部的，都要仔细考虑。<br>同理，拆分reducer也是个麻烦，哪些reducer要管理哪些状态。</p>
<p>目前我的做法是每个组件一个reducer，维护每个组件的全局状态。整体的state就是所有组件的全局状态组合起来，类似这种：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 整体的初始状态</span></div><div class="line"><span class="comment">// 就是把每个组件自己的初始状态组合起来, 注意key的名字和组件名一致</span></div><div class="line"><span class="keyword">const</span> initState = {</div><div class="line">  Sidebar: Sidebar.initState,</div><div class="line">  Login: Login.initState,</div><div class="line">};</div></pre></td></tr></table></figure>

<p>不知道这样做好不好。</p>
<ul>
<li>会不会有并发的问题？</li>
</ul>
<p>换句话说，<code>store.dispatch()</code>是同步还是异步？如果同时触发多个action，处理顺序会错乱么？之所以想到这个问题，是因为<a href="https://facebook.github.io/react/docs/react-component.html#setstate" target="_blank" rel="external">react文档</a>说setState可能是异步的：</p>
<p><blockquote>setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.</blockquote><br>相关分析见<a href="https://www.bennadel.com/blog/2893-setstate-state-mutation-operation-may-be-synchronous-in-reactjs.htm" target="_blank" rel="external">这里</a>。我猜react这么设计可能是为了提升<a href="https://facebook.github.io/react/docs/forms.html#controlled-components" target="_blank" rel="external">Controlled Components</a>的性能？毕竟Controlled Components用起来很蛋疼，用户每输入一个字符就render一次完全没必要。。。</p>
<p>现在<a href="https://github.com/reactjs/redux/blob/master/src/createStore.js#L149" target="_blank" rel="external">dispatch的实现</a>是同步的，也就是说一个action必须reduce完毕才能开始下一个action。而且js本身就是单线程的，应该不会有race condition。但代码中却有一个isDispatching变量，和这样一段代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (isDispatching) {</div><div class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Reducers may not dispatch actions.'</span>)</div><div class="line">}</div></pre></td></tr></table></figure>

<p>理论上来说，这个错误应该永远不会触发啊。不可能同时处理两个action。所以这个isDispatching是啥意思。。。</p>
<ul>
<li>状态变化如何反应到组件上？</li>
</ul>
<p>换句话说，如何做数据绑定？总不会是像angular一样的dirty-check。。。</p>
<p>从<a href="https://github.com/reactjs/redux/blob/master/src/createStore.js#L176" target="_blank" rel="external">代码</a>上来看，每次状态变化后，会立刻调用所有listener（同步调用），这时listener就能感知到状态变化了。这里有一个问题：每个listerner所关心的状态是不同的，为啥要所有listener轮询一次呢？如果能知道哪些状态变化了，再触发对应的listener，是不是效率更高？这还是一个为啥不用publish-subscribe模型的问题。。。</p>
<p>不过那样就需要redux去做diff算法了，必须知道状态中哪些字段变化了，反而让store变得更复杂。也许跟redux追求简单的设计哲学不符吧。<br>话说，高效diff两个对象，好像Immutable.js中提供了一种专用的数据结构？</p>
<p>但对于<code>react-redux</code>而言，根据我的测试，每次store的变化都会触发所有组件的re-render。。。即是说，真正的listener是App组件（最上层组件）的render方法，每次状态变化都会导致App re-render，进而导致所有子组件re-render。。。这样真的不会有性能问题嘛？有些组件根本就跟store里的状态没关系（没有用connect方法包装），根本就没有必要re-render吧。</p>
<p>我本来还在想，redux根据什么知道哪些组件需要哪些状态？根据什么检测到状态的变化导致特定组件重新render？结果它直接简单暴力的全部re-render。。。看来还是我想多了。。。但这样真的有性能隐患的，大量无用的re-render会的导致频繁的dom-diff。虽然我们可以通过<a href="https://www.npmjs.com/package/pure-render-decorator" target="_blank" rel="external">pureRender</a>/<a href="https://facebook.github.io/react/docs/optimizing-performance.html#shouldcomponentupdate-in-action" target="_blank" rel="external">PureComponent</a>/<a href="https://facebook.github.io/immutable-js/" target="_blank" rel="external">Immutable.js</a>之类的手段去优化shouldComponentUpdate方法，去避免这种系统能损失，但总归增加了额外的工作量和复杂度。</p>
<p>归根结底一句话，redux为啥不用publish-subscribe模型啊。。。</p>
<p>另外说下中间件，redux的中间件有点类似JavaEE的filter，也是继承了redux一贯的简洁风格。中间件可以用如下的形式来表示：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">var</span> anyMiddleware = <span class="function"><span class="keyword">function</span> <span class="params">({ dispatch, getState })</span> </span>{</div><div class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span><span class="params">(next)</span> </span>{</div><div class="line">        <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="params">(action)</span> </span>{</div><div class="line">            <span class="comment">// 中间件相关代码</span></div><div class="line">            <span class="comment">// 这个next和JavaEE中的filterChain.doFilter作用类似</span></div><div class="line">        }</div><div class="line">    }</div><div class="line">}</div></pre></td></tr></table></figure>

<p>所以可以按自己的需要很方便的写一些特定的中间件。<br>另外要注意下中间件的次序。</p>
<p>redux中最常用的中间件应该就是<a href="https://github.com/gaearon/redux-thunk" target="_blank" rel="external">redux-thunk</a>和<a href="https://github.com/acdlite/redux-promise" target="_blank" rel="external">redux-promise</a>了，二者都是用来做一些异步操作的。细心些就会发现，这我们上面说的用生成器做异步操作的两种方式（thunk/promise）正好是对应的。这其实算不上什么巧合，因为对于异步操作，redux和生成器关心的问题是类似的。生成器关心的是“异步操作结束后如何要回代码执行权”，redux关心的是“异步操作结束后如何触发新的action”。所以他们的解决方法也都类似。</p>
<p>重点说说Thunk中间件。redux中的thunk相当于一种特殊的action creator。普通的action creator直接返回action对象，而thunk会返回一个function（入参是dispatch），我们就可以在这个function中做异步操作，异步操作结束后重新dispatch了。store.dispatch时如果发现入参是function，就会执行它。原理其实非常简单，代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 这个中间件说穿了就一行代码。。。但能想到这个方法才是关键</span></div><div class="line"><span class="keyword">var</span> thunkMiddleware = <span class="function"><span class="keyword">function</span><span class="params">({dispatch,getState})</span></span>{</div><div class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span><span class="params">(next)</span></span>{</div><div class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span><span class="params">(action)</span></span>{</div><div class="line">      <span class="keyword">return</span> <span class="keyword">typeof</span> action === <span class="string">'function'</span> ? action(dispatch,getState):next(action);</div><div class="line">    }</div><div class="line">  }</div><div class="line">}</div></pre></td></tr></table></figure>

<p>一个例子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 普通的creator</span></div><div class="line"><span class="keyword">const</span> normalActionCreator = () =&gt; {</div><div class="line">  <span class="keyword">return</span> {type: <span class="string">'INCREMENT'</span>};</div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">// 异步的creator, 用setTimeout模拟网络请求</span></div><div class="line"><span class="keyword">const</span> asyncActionCreator = () =&gt; {</div><div class="line">  <span class="comment">// 其实返回的函数, 可以有两个参数的, (dispatch, getState)</span></div><div class="line">  <span class="comment">// 还可以自定义额外参数, 见https://github.com/gaearon/redux-thunk#injecting-a-custom-argument</span></div><div class="line">  <span class="keyword">return</span> (dispatch) =&gt; {</div><div class="line">    setTimeout(() =&gt; {</div><div class="line">      <span class="comment">// 过一秒之后触发一个正常的action</span></div><div class="line">      dispatch(normalActionCreator());</div><div class="line">    }, <span class="number">1000</span>);</div><div class="line"></div><div class="line">    <span class="comment">// thunk函数里可以再次dipatch thunk</span></div><div class="line">    <span class="comment">// dispatch(asyncActionCreator());   // 纯属测试</span></div><div class="line"></div><div class="line">    <span class="comment">// 关于store.dispatch的返回值</span></div><div class="line">    <span class="comment">// 如果传入的参数是普通对象, 返回值就是那个对象</span></div><div class="line">    <span class="comment">// 如果传入的参数是函数, 就是函数执行后的结果, 如果函数中没有明确的return语句, 就返回undefined</span></div><div class="line">    <span class="keyword">return</span> <span class="string">'dispatch'</span>;</div><div class="line">  };</div><div class="line">};</div></pre></td></tr></table></figure>

<p>参考资料：</p>
<p><a href="https://github.com/zalmoxisus/redux-devtools-extension" target="_blank" rel="external">Redux DevTools Extension</a>：调试redux必备</p>
<p>ruanyifeng的redux系列教程：<a href="http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html" target="_blank" rel="external">1</a>/<a href="http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html" target="_blank" rel="external">2</a>/<a href="http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html" target="_blank" rel="external">3</a>。</p>
<p>关于action格式的社区规范：<br><a href="https://github.com/acdlite/flux-standard-action" target="_blank" rel="external">https://github.com/acdlite/flux-standard-action</a><br><a href="https://github.com/acdlite/redux-actions" target="_blank" rel="external">https://github.com/acdlite/redux-actions</a></p>
<p>其他一些react/redux的文档：<br><a href="http://cn.redux.js.org//docs/introduction/index.html" target="_blank" rel="external">http://cn.redux.js.org//docs/introduction/index.html</a><br><a href="https://github.com/react-guide" target="_blank" rel="external">https://github.com/react-guide</a><br><a href="https://github.com/camsong/redux-in-chinese" target="_blank" rel="external">https://github.com/camsong/redux-in-chinese</a><br><a href="http://stackoverflow.com/questions/36085726/setstate-in-reactjs-is-async-or-sync" target="_blank" rel="external">http://stackoverflow.com/questions/36085726/setstate-in-reactjs-is-async-or-sync</a><br><a href="https://github.com/camsong/blog/issues/3" target="_blank" rel="external">Immutable 详解及 React 中实践</a><br><a href="https://github.com/lcxfs1991/blog/issues/8" target="_blank" rel="external">React移动web极致优化</a></p>
<h1 id="前端是个大坑">前端是个大坑</h1>
<p>我虽然一直戏称自己是二手前端，但其实对前端也研究了不少了，尤其是跳了react的坑之后。<br>千言万语汇成一句话：前端是个大坑。而且比我目前跳过的任何坑都深。。。如果对自己的“抗折腾”能力没自信，千万不要入坑。<br>虽然已经有无数人吐槽过了，但我还是忍不住要吐槽下。。。槽点多不可怕，关键是总是有新的槽点出现，这就很蛋疼了，再专业的吐槽也遭不住啊。</p>
<ul>
<li>总是有各种稀奇古怪的新东西</li>
</ul>
<p>昨天还说promise要一桶浆糊，今天就说async/await是终极解决方案了。<br>昨天还是react集万千宠爱，今天就开始一股脑吹捧vue了。<br>还有fetch，这货用起来还一堆问题，timeout都不支持，就已经有人在叫着ajax已死了。<br>npm还没搞明白，又跳出来一个<a href="https://code.facebook.com/posts/1840075619545360" target="_blank" rel="external">yarn</a>。<br>同构应用？怎么突然开始流行这个概念了？<br>服务端渲染？感觉又是一个大坑。<br><a href="http://webassembly.org/" target="_blank" rel="external">WebAssembly</a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/Web_Components" target="_blank" rel="external">WebComponents</a>，这都是什么鬼？</p>
<p>总结下就是：心累。。。不过新技术很多，至少说明这个领域一直在发展，我还算可以接受。</p>
<ul>
<li>缺少规范</li>
</ul>
<p>在研究redux的时候，工程结构又让我纠结了好久。。。有人说reducers代码放一个单独的文件夹，actions放一个单独的文件夹，但也有人说放在一起。然后把组件强制分为“显示组件”、“逻辑组件”？显示组件里才有render方法，只从props里读数据并显示；逻辑组件里只有一个connect方法。。。不蛋疼么。。。<br>更别提webpack的各种坑爹配置了。</p>
<p>总之就是每个人都有自己的做事方法，缺少一个统一的规范。在合作大型项目的时候，真的不会有问题么？本来么，没有规范大家商量出来一个就可以了，各方妥协下，但总是有人认为自己的方式就是比别人好，我也只能呵呵。。。这应该说是有傲骨呢还是情商低呢。。。</p>
<p>以后前端领域会不会细分出来一个角色：“Dev Environment Administrator”，专门负责维护开发环境，各种配置都丢给他去搞。。。就像现在的SA一样。</p>
<ul>
<li>js果然还是不适合协作？</li>
</ul>
<p>同一个组件我有6种写法你信不？</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 函数式组件</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Test1</span><span class="params">(props)</span> </span>{</div><div class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="title">div</span>&gt;</span>hello, {props.name1}, {props.name2}<span class="tag">&lt;/<span class="title">div</span>&gt;</span>;</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 函数式组件+解构</span></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">Test2</span><span class="params">({name1, name2})</span> </span>{</div><div class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="title">div</span>&gt;</span>hello, {name1}, {name2}<span class="tag">&lt;/<span class="title">div</span>&gt;</span>;</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 还是函数式组件，但换成箭头函数的写法，可以省略return语句</span></div><div class="line"><span class="comment">// 普通函数组件不能省略return</span></div><div class="line"><span class="keyword">const</span> Test3 = props =&gt; <span class="xml"><span class="tag">&lt;<span class="title">div</span>&gt;</span> hello, {props.name1}, {props.name2}<span class="tag">&lt;/<span class="title">div</span>&gt;</span>;</span></div><div class="line"></div><div class="line"><span class="keyword">const</span> Test4 = ({name1, name2}) =&gt; <span class="xml"><span class="tag">&lt;<span class="title">div</span>&gt;</span>hello, {name1}, {name2}<span class="tag">&lt;/<span class="title">div</span>&gt;</span>;</span></div><div class="line"></div><div class="line"><span class="comment">// 这是最正常的写法</span></div><div class="line"><span class="keyword">class</span> Test5 extends React.Component {</div><div class="line">  render() {</div><div class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="title">div</span>&gt;</span>hello, {this.props.name1}, {this.props.name2}<span class="tag">&lt;/<span class="title">div</span>&gt;</span></span></div><div class="line">  }</div><div class="line">}</div><div class="line"></div><div class="line">// 这是装逼的写法</div><div class="line">class Test6 extends React.Component {</div><div class="line">  // 为了装逼,箭头函数的参数用一个_接收</div><div class="line">  // 别笑, 我真的见过有人这么写</div><div class="line">  // 没有参数就老老实实写作() =&gt; 不好么, 非要写成 _ =&gt;</div><div class="line">  // 你是lodash用多了吧</div><div class="line">  render = _ =&gt; <span class="tag">&lt;<span class="title">div</span>&gt;</span>hello, {this.props.name1}, {this.props.name2}<span class="tag">&lt;/<span class="title">div</span>&gt;</span>;</div><div class="line">}</div><div class="line"></div><div class="line">ReactDOM.render(</div><div class="line">  <span class="xml"><span class="tag">&lt;<span class="title">Test6</span> <span class="attribute">name1</span>=<span class="value">"jxy"</span> <span class="attribute">name2</span>=<span class="value">"xy"</span>/&gt;</span>,</span></div><div class="line">  document.getElementById('root')</div><div class="line">);</div></pre></td></tr></table></figure>

<p>这6种写法居然是一样的效果你敢信？一毛一样啊。让我想到孔乙己的“回字有4种写法”。。。你要是对ES6不熟，看到这一堆货肯定直接懵逼。</p>
<p>如果了解过一些python vs perl在语言哲学上的区别，就可以知道js和perl类似，也是典型的<code>many ways to do one thing</code>。换句话说，可以玩出非常多的花样，非常灵活。我在写js的时候就特别激进，会尝试使用各种新特性，一遍遍的重构代码；但我写java的时候就很保守。。。说实话很多java8甚至java7的特性都还没用过。。。毕竟生产环境也只是jdk7而已。</p>
<p>但对于协作而言，这种灵活性就可能导致问题。每个人的思路、代码风格都千差万别，有些人就很喜欢用function component，有些人就喜欢用普通的class。<br>在这种情况下，统一的编码规范或者说是“最佳实践”非常重要。啥是最佳实践？不同的人去实现同样的功能，他们的实现是类似的，达到这个效果就可以。毕竟大型协作项目中要的不是炫技，不是各种hack，而是可读性、可维护性。这种哲学在python中其实是非常常见的，python可以说直接从语言层面给出了最佳实践。<br>比如对于react组件，我就倾向于不要使用函数式组件，全都用class去定义。这样虽然会有冗余代码，但代码很“规整”，更清晰。</p>
<ul>
<li>啥时候能不用写css啊</li>
</ul>
<p>对css实在是无力吐槽了，折腾起来太麻烦了。布局居然有好几种方式：float/position/flex之类。我前段时间还特意把css布局的教程从头到尾看了一遍，以为已经是天下无敌了，结果过段时间就全部忘掉了。。。<br>这玩意如果不是天天用，根本记不住。只能是头痛医头脚痛医脚，哪里显示有问题就打开chrome慢慢调试。所谓的调试也是在凭感觉乱搞，<code>display:block</code>不对？那就试试<code>display:inline-block</code>；<code>position:absolute</code>有问题？那就试试<code>position:relative</code>，要不再加个<code>float</code>；边界有问题？那就<code>left/right-margin/padding</code>统统搞一遍试试。。。<br>作为二手前端，css是最让我头疼的东西了。据说很早以前写css和写js的人是分开的？设计师做好页面写好css再让程序员去填写逻辑。不过那可能是静态网页时代的做法了。。。</p>
<p>题外话，我之前一直疑惑<code>npm run start</code>和<code>npm start</code>有啥区别，后来发现原来start和test对npm而言是特殊处理的。。。见<a href="https://blog.jayway.com/2014/03/28/running-scripts-with-npm/" target="_blank" rel="external">这里</a>：<code>In addition to the shell&#39;s pre-existing PATH, npm run adds node_modules/.bin to the PATH provided to scripts.</code><br>这tm也是个隐藏很深的坑。。。</p>
<h1 id="杂七杂八">杂七杂八</h1>
<p>google协程的过程中，顺便了解了很多并发相关的事情。如果不考虑多核（并行），只考虑单核下分时间片执行（并发）的情况下，并发最大的代价就是上下文的切换。所以并发模型的演进（多进程-&gt;多线程-&gt;Event Loop-&gt;协程）其实一直致力于减少上下文切换的开销。参考：<a href="http://yanyiwu.com/work/2014/12/20/c-coroutine.html" target="_blank" rel="external">1</a>/<a href="https://segmentfault.com/a/1190000001813992" target="_blank" rel="external">2</a>。<br>其实并发是个很复杂的问题，还涉及到指令乱序/重排、内存可见性等等蛋疼事，可以看下<a href="http://www.ituring.com.cn/book/1649" target="_blank" rel="external">《七周七并发模型》</a>。</p>
<p>看<a href="http://www.infoq.com/cn/news/2016/12/What-architect-do" target="_blank" rel="external">某篇文章</a>时突然想到，到底啥是架构？<br>我一直觉得“架构”是个很容易有水份的词。架构本质上是一种抽象，它的抽象能力必须足够强大，才能承载各种各样形态的具体业务。如果业务超出了你的架构的抽象能力，要么对系统做“大手术”，要么削减业务（有点削足适履的感觉）。<br>说的通俗点，什么是好的架构？就是迥然不同的业务逻辑，系统的底层却是完全相同的。搞零售的和玩期货的，用的是同样的底层系统；每天一单的普通个人店铺，和峰值十万单的大V，是同样的底层系统；一口价和团购，用的还是同样的底层。从这种角度上来说，DNA-蛋白质这套系统，算是个好架构吧。。。不过这个与其说是架构，更像是“语言”。</p>
<p>但是如果抽象过度了，架构也就没啥用了。借用一张“神图”：<br><img src="/2017/01/15/2017-and-redux/2.jpg" alt=""><br>这张图是很久之前在微博上看到的，好像是在讲云计算什么的。这张图的“神”就在于，你可以在所有的分布式系统、云计算、甚至是什么组织结构、神经系统之类上面套用这张图，反正就是一个中心节点+一堆子节点么。。。如果你敢跟老板说这就是你的系统的架构图，信不信分分钟被打出去。。。这张图生动的告诉了我们，什么是“正确的废话”。<br>所以，架构对业务的抽象，必须限定在一定的范围内，至少要让人看明白吧。越抽象就会丢失越多细节，如何把握这个“度”，才是我们应该思考的。<br>另外注意要划分清楚系统的边界（业务边界），不可能有一个系统能承载所有的业务的，要知道哪些该做/哪些不该做。</p>
<p>一般而言，当我们接到需求时，首先要从业务的角度出发去看，梳理清楚所有的业务规则，保证整个业务流程是通畅的，能走通的；然后再去考虑如何去实现业务逻辑，如何让各个业务规则的实现不要互相干扰并且便于扩展。也就是先有需求再有架构。<br>尤其不要依赖于具体的技术实现，这对以后的系统改造也会方便很多，比如php迁移到java之类的。<br>但很多人看到需求总是会直接联想到代码。。。这其实是有问题的。。。<br>另一个很容易犯的错误：代码依赖于具体的数据库/应用服务器实现，比如依赖于mysql+tomcat，也会为日后的扩展埋下隐患。</p>
<h1 id="总结？">总结？</h1>
<p>怎么写了这么多。。。最近乱七八糟的研究了很多东西。其实我本来是想学下redux的，不知道怎么回事就开始研究async/await、生成器了。。。<br>发现我如果学什么东西，一不小心就会变成DFS。看到一个新的概念就会去google，然后偏离主题。。。最后才想起来自己本来想学啥。这样会不会栈溢出啊。。。</p>
<p>之前搞的<a href="https://github.com/jiangxy/react-antd-admin" target="_blank" rel="external">React后台</a>意外收获近100个star，受宠若惊，也帮助了一些人。看来不能拖更了。</p>
<p>数了下2016年只写了9篇blog。。。比我想的少很多。<br>好长时间没有刷算法题了。。。我的<a href="https://www.gitbook.com/book/jiangxy/leetcode-gossip/details" target="_blank" rel="external">leetcode题解</a>也坑了好久。<br>果然懒惰才是我最大的敌人啊。。。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p><center>转眼已是经年</center><br>]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
      <category term="react" scheme="http://jxy.me/tags/react/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[不务正业again]]></title>
    <link href="http://jxy.me/2016/12/07/wx-applet/"/>
    <id>http://jxy.me/2016/12/07/wx-applet/</id>
    <published>2016-12-07T10:06:50.000Z</published>
    <updated>2016-12-20T11:21:25.000Z</updated>
    <content type="html"><![CDATA[<p>又一次不务正业，研究了下微信小程序。</p>
<p>其实微信小程序已经火了好久了。几个月之前内测的时候，很多人就迫不及待去研究，各种破解资料、教程满天飞。公司内部也专门组织人去学习了下。<br>可惜当时我事情比较多，只是打了个酱油，等公测开始才真正去研究了下。<a href="https://github.com/jiangxy/wxapplet-canvas-demo" target="_blank" rel="external">写了个简单的小游戏</a>，算是对小程序有了个大概的了解。话说最好的学习方式果然还是实践。古人云：“纸上得来终觉浅，须知此事要躬行”。</p>
<p><strong>本文不是一个教程</strong>，只是总结下学习过程中的感想。</p>
<a id="more"></a>
<h1 id="初印象">初印象</h1>
<p>其实最早的传闻，是微信要做一个叫“服务号”的东西，老大说要我们关注下，但完全没有头绪，网上也没有啥靠谱的资料。</p>
<p>题外话，我一直觉得微信的生态好复杂，订阅号/服务号/企业号傻傻分不清楚，公众平台/开放平台到底是啥关系，工作中碰到各种openId/unionId、各种授权的问题也总是会晕。而且这个生态特别封闭，自成体系，微信会管的很多，比如外链的限制、对外部接口的限制等等。这倒是和appstore有点像。<br>在这种封闭的生态中做开发，其实挺难受的。。。但生态封闭、开发难受，用户体验就能得到保证。参考ios vs android。而且，能做出这样一个生态真是挺NB的，不得不服。</p>
<p>另外，总有人纠结HTML5 vs H5，我觉得这种纠结很无聊啊。写作<code>HTML5</code>就比<code>H5</code>高大上了？有人看到H5就一定要去纠正一下：“你这样是不对的，应该是<code>HTML5</code>，H5是中国人自己发明的叫法”，以此彰显自己的专业。知乎上这种风气尤甚。你咋不说茴字有四种写法呢╮(╯_╰)╭？嘛，你们开心就好，我反正是个二手前端，图省事一般都叫做H5。</p>
<p>小程序的<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/framework/structure.html?t=20161122" target="_blank" rel="external">结构</a>还是比较简单的，无非是<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/?t=20161122" target="_blank" rel="external">WXML</a>和<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxss.html?t=20161122" target="_blank" rel="external">WXSS</a>解决视图层的问题，JS解决逻辑层的问题。WXML和WXSS可以认为是HTML和CSS的方言，只是有些特殊的限制，其实经过编译之后还是HTML和CSS。这套逻辑和各种概念早就被各种JS的MVVM框架玩烂了，实在没啥新鲜的。<br>WXML除了常规的数据绑定/模版/引用等概念，值得注意的是<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/event.html?t=20161122" target="_blank" rel="external">事件绑定</a>，和普通的HTML不同，不过用起来挺顺手的。<br>WXSS和CSS没啥区别，写起来还是一样的痛苦。。。我本来还期望着微信能有什么方法解决布局的问题，释放二手前端生产力。。。为啥前端一直没有一种可视化的布局工具嘞？像若干年的VB6一样，拖拖拽拽就能搭出一个页面。mac上有个软件<a href="http://www.maczapp.com/sparkle-pro-visual-web-design" target="_blank" rel="external">Sparkle</a>实现了类似的想法，可是不太好用。</p>
<p>因为我之前有研究过一点React，于是学习小程序的过程中总是会不自觉的和React对比。小程序也确实和React有点像。比如<code>setData</code>明显就是抄的<code>setState</code>，还有组件化/生命周期等概念。但用户无法自定义组件是一大问题，而且只有页面级别的组件，也许这是微信为了降低开发复杂度而做的妥协吧。<br>应该说，页面是唯一的“容器组件”，所有的状态都在页面组件中。另外<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/page.html?t=20161122" target="_blank" rel="external">页面路由</a>的管理也真是很迷。。。经常有些诡异的状况出现。</p>
<p>在视图层，微信提供了一些可用的<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/component/?t=20161122" target="_blank" rel="external">基础组件</a>，这些应该被称为“UI组件”（感觉是脱胎于<a href="https://mp.weixin.qq.com/wiki/2/ae9782fb42e47ad79eb7b361c2149d16.html" target="_blank" rel="external">WeUI</a>），最大意义在于提供统一的视觉风格，跟React的组件不是一个概念。还不是很完善，尤其是对比Ant Design之类的。不知道什么时候会有小程序的第三方UI KIT。</p>
<p>小程序在WXML中绑定数据的逻辑又和angular有点像。不过我挺喜欢<a href="http://mustache.github.io/" target="_blank" rel="external">Mustache</a>的语法的，有点像jsx表达式，但区别也很多。<br>WXML中的模版概念，感觉也是从angular中搞过来的。</p>
<p>总的来说，小程序开发框架（传说这个框架名字叫MINA？）就是个大杂烩，吸收了各种JS框架的理念，同时加入一些自己<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/?t=20161122" target="_blank" rel="external">特有的API</a>（我要吐槽微信的canvas API真是超难用，跟标准的canvas完全不一样，让我找到了小学用logo语言画图的感觉。。。）。<br>如果你之前使用过一些MVVM框架，那小程序完全没啥学习成本，直接就能上手。<br>就算之前没有接触过，相对于让人望而生畏的厚厚一本《Angular JS实战》/《深入React技术栈》，小程序对新手无疑也要友好很多。</p>
<h1 id="一些思考">一些思考</h1>
<p>由于工作原因，我接触过一些<a href="https://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html" target="_blank" rel="external">JS-SDK</a>。在我看来，使用了JS-SDK的H5页面已经可以很接近原生app的体验了，可以调用各种原生app的能力，除了慢点。参考微信自带的理财通。</p>
<p>那问题来了，微信小程序就是加强版的JS-SDK么？小程序和普通的H5区别在哪里？为啥微信要再造个轮子？</p>
<p>我的感觉：<br><strong>1.运行时&amp;效率的不同</strong><br>这是最重要的区别。之前有消息说小程序使用了ReactNative。我以为是编译成native代码执行，但目测不是啊。摘自微信官方文档：</p>
<blockquote>微信小程序运行在三端：iOS、Android 和 用于调试的开发者工具<br><br>在 iOS 上，小程序的 javascript 代码是运行在 JavaScriptCore 中<br>在 Android 上，小程序的 javascript 代码是通过 X5 内核来解析<br>在 开发工具上， 小程序的 javascript 代码是运行在 nwjs（chrome内核） 中</blockquote>

<p>看来小程序的代码还是要有一个JS运行时环境去支持的。但微信应该有做过一些优化，坊间传言视图层（WXML中的块级元素）的渲染都是native的。否则打开微信小程序=扫码打开一个网页，那也太low了吧。。。至于是否有其他优化策略，还不得而知。<br>而普通的H5么，因为是用浏览器打开，效率明显会低一些。iOS上微信会调用系统自带的浏览器内核，Android上会使用内嵌的QQ浏览器X5内核（所以一直被吐槽是手机上的IE6）。</p>
<p><strong>2.简化&amp;规范开发流程，约定大于配置</strong><br>相比开发一个H5页面，开发一个小程序的难度是很低的。摘自微信官方文档：</p>
<blockquote>小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。</blockquote>

<p>如果想用H5+JS-SDK实现类似于原生APP的体验，无疑是超级麻烦的，光是各种设备的适配就能搞个半死。。。而且使用标准的HTML时，要去关注很多跟业务逻辑无关的东西，比如各种<code>&lt;meta&gt;</code>标签、<code>&lt;script&gt;</code>的顺序、CSS属性的各种特定前缀之类的。<br>而小程序将这些复杂的事情都交给框架去做，同时对工程结构做了很多约定，简化了开发流程。从某种意义上说，JS-SDK只是个“工具”，而小程序才是真正的“框架”，就是jquery和react的区别。<br>这也是所谓的DRY原则。</p>
<p><strong>3.重新整合微信特有的API</strong><br>小程序提供了各种微信特有的API，比如登录/微信支付/照片/设备信息等等。我对比了下JS-SDK提供的API，发现二者是互相交叉的，小程序去掉了JS-SDK中很多用不到的API，而且小程序的API也好用很多。或者说，原来的JS-SDK的API设计的太烂了。。。用过的没有不吐槽的。</p>
<p>不过小程序应该也从JS-SDK借鉴了很多经验。</p>
<p><strong>4.增加对生态的控制力</strong><br>微信的生态一直是封闭的。而H5很难控制，你不知道一个页面是做什么用的，打开之后会发生什么，请求哪些服务端，也很难封杀。同样的页面内容，完全可以换个马甲就出来了。。。从战略上来说，对整个生态是不利的。<br>而小程序请求哪些域名、能做什么事、呈现什么内容，可以说完全在微信的控制之下。尤其是上架还要经过审核，想改就改，想封杀就封杀。越来越像老大哥了啊。。。对于所有的权利集团而言，当然是权利越集中对自己越有利啊。</p>
<p><strong>5.渠道的问题</strong><br>微信小程序的入口现在还不清楚。有说是二级菜单的，有说是像公众号一样的，甚至有说可以直接放在系统桌面的（估计只有android能做到这种吧，桌面widget）。不过不管怎样，相比只能在朋友圈里以url形式传播的H5页面，小程序的渠道肯定更丰富吧，离用户更近，也就更容易获得流量。</p>
<p>说到底，大家为什么这么看好小程序，还不是因为微信巨大的流量，9亿的注册用户，5亿的月活，还自带社交渠道的推广属性，怎么想都是很诱人。关键就看腾讯爸爸怎么分配入口和流量了。。。如果能拿到更核心的社交关系数据，想象空间更是无限大，不过这个腾讯肯定是不会给的，毕竟隐私策略也是个大问题。</p>
<p>个人感觉，小程序的关键在于“小”。小就意味着简单：开发简单，不需要投入太多；使用简单，用户进入后很快找到需要的功能，不会有太多的干扰因素；功能上也应该简单，即开即用，用完就走，不需要有什么顾虑。很多公司开发小程序，就是把自己已有的APP原样照搬过去，俺对此持保留意见。。。<br>感觉上，用户在打开一个小程序之前，就应该知道要干什么，知道要打开哪个程序。程序被打开后，让用户做完想做的事就可以了，而不要提供各种乱七八糟的额外功能。小程序应该专注于特定的本职功能。<br>所以我觉得一些工具类/游戏类的小程序会比较契合主题，一些“大而全”的小程序反而是理念有问题。可以参考下小程序的<a href="https://mp.weixin.qq.com/debug/wxadoc/design/index.html?t=20161109" target="_blank" rel="external">设计指南</a>。</p>
<p>还有啊，为啥大家默认小程序就是H5页面？各种视觉、交互都是按照网页的风格去设计，很多原来的在线H5设计工具，改一改就号称是小程序在线设计了。。。如果打开小程序就等于是打开了一个浏览器，那还有啥意思。<br>我理想中的小程序，应该是体验和native app一样的，用户打开app时，不应该明显的感觉到从app进入到了一个网页。用户都不傻的，“画风突变”还是能分别出来的。<br>我在使用一些APP时，碰到这种画风突变的情况，也会很蛋疼。。。不管你背后的技术是native还是H5还是hybird，至少体验要一致吧。</p>
<p>小程序一出来，很多人就叫嚣着“H5要取代app”，“H5开发需求要爆发”，“微信将统一所有APP”，“ios/android开发要失业”。。。嗯，开心就好。。。<br>这让我想到Facebook Messenger刚推出<a href="https://developers.facebook.com/docs/messenger-platform/product-overview" target="_blank" rel="external">Bot平台</a>时，大家也是各种激动，号称以后各种APP都将消亡，很多用户需求都将在Bot上满足，人工智能将迎来春天。甚至Bot上将形成自己的appstore。<br>乐观是好的，但忽悠人就不对了。客观的说，冲击肯定是有的，但说取代APP还为时过早。微信对APP的冲击从公众号时代就开始了。现在我们试水一个新业务，很多都是先做一个公众号而不是APP。至于小程序会带来哪些改变，谁知道呢，上帝的归上帝，凯撒的归凯撒吧。对个人而言，多关注些业界，多了解些技术总是不会错的，但也没必要过度反应。</p>
<p>之前不是也有好多人担心微信能不能过appstore审核。如果微信小程序真的能发展到威胁appstore的程序，苹果也不会坐视不理吧。之前为啥ios封杀flash，安全/性能只是一方面，更大的原因是flash“不受控”，adobe完全可以在flash上发展出自己的生态，flash生态中的应用/用户/数据，都完全不受控制。</p>
<p>不管怎样，拭目以待吧。</p>
<h1 id="Hybird">Hybird</h1>
<p>我对APP开发的经验是0，仅有的一点点经验来自React Native。但平常跟各个APP的同学打交道还是比较多的，也知道一些概念。对Hybird这个概念一直比较模糊，正好顺道google下。</p>
<p>Hybird也就是所谓的混合开发，一个APP中，部分使用native实现，部分使用H5实现。一般而言展示性强的页面偏向于H5，功能性强的页面偏向于native。但也没有定式。好处嘛就是降低开发成本，但出现问题调试也比较麻烦。<br>我以前一直以为所谓的Hybird APP就是一个浏览器。。。所有的页面都是H5的，app本身只负责提供一些底层的接口。看了<a href="http://www.infoq.com/cn/articles/hybrid-app-development-combat" target="_blank" rel="external">这篇文章</a>才知道，Hybird也分很多种，H5甚至可以只负责局部的渲染。</p>
<p>感觉上，React Native应该不算Hybird吧，毕竟它是编译成原生代码。有点像编译型语言和解释型语言的区别。</p>
<p>H5和native通信的组件被称为JsBridge，但似乎没有一个标准的规范？各家APP都有自己的实现，个人没事也可以写一个玩，基本就是在原生的WebView上各种hack，拦截H5页面请求的一些特殊的url（比如<code>weidian://methodName?k1=v1&amp;k2=v2</code>）再做出相应的处理。微信的JS-SDK调用native API也是同样的道理。<br>通过JS调用java方法，想想还是挺带感的。。。</p>
<p>话说，我最早记住的Hybird开发工具是<a href="https://www.xamarin.com/" target="_blank" rel="external">Xamarin</a>，忘了是从哪里看到的了。我一看这名字，“厦门人”，哎呦有个性啊。。。</p>
<h1 id="参考资料">参考资料</h1>
<p>小程序现在不需要去看第三方的破解资料了，直接看<a href="https://mp.weixin.qq.com/debug/wxadoc/introduction/index.html?t=20161122" target="_blank" rel="external">官方文档</a>就好。</p>
<p>腾讯云的小程序解决方案，能解决很多问题，session/websocket/https证书/域名之类的，但要有内测资格才能使用，而且收费：<br><a href="https://www.qcloud.com/solution/la.html" target="_blank" rel="external">https://www.qcloud.com/solution/la.html</a><br><a href="https://github.com/tencentyun/weapp-solution" target="_blank" rel="external">https://github.com/tencentyun/weapp-solution</a></p>
<p>两个用于小程序界面设计的在线工具：<br><a href="http://www.coolsite360.com/wxapp/" target="_blank" rel="external">http://www.coolsite360.com/wxapp/</a><br><a href="http://jisuapp.cn/" target="_blank" rel="external">http://jisuapp.cn/</a></p>
<p>一个小程序论坛，有很多开源代码：<br><a href="https://weappdev.com/" target="_blank" rel="external">https://weappdev.com/</a></p>
<p>对小程序比较透彻的分析：<br><a href="https://github.com/phodal/weapp-quick" target="_blank" rel="external">https://github.com/phodal/weapp-quick</a></p>
<p>一个比较好的小程序教程：<br><a href="https://segmentfault.com/a/1190000007033827" target="_blank" rel="external">https://segmentfault.com/a/1190000007033827</a></p>
<p>两个纯用canvas做的小游戏，当作canvas入门挺好的：<br><a href="https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript" target="_blank" rel="external">https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript</a><br><a href="http://www.w3schools.com/graphics/game_intro.asp" target="_blank" rel="external">http://www.w3schools.com/graphics/game_intro.asp</a></p>
<p>CSS布局教程，对我这样CSS苦手的人是一大福音：<br><a href="http://zh.learnlayout.com/toc.html" target="_blank" rel="external">http://zh.learnlayout.com/toc.html</a></p>
<h1 id="碎碎念">碎碎念</h1>
<ol>
<li>JS真是无所不在，不光能写移动端的，还能写桌面端的。。。微信官方的开发工具其实就是个react应用，见<a href="https://www.phodal.com/blog/weapp-demo-process-webkit-app/" target="_blank" rel="external">这篇文章</a>的分析。这种技术被称为<a href="http://nwjs.io/" target="_blank" rel="external">node-webkit</a>，感觉就是桌面版的Hybird啊，有空研究下。</li>
<li>canvas是个好东西。理论上在canvas中可以做任意事情，发挥自己的想象力。Flipboard甚至搞出个<a href="https://github.com/Flipboard/react-canvas" target="_blank" rel="external">react-canvas</a>，所有UI都在canvas上绘制，真是有毅力。据说是为了避免DOM的性能问题？有空研究下。</li>
<li>强烈要求微信改进开发者工具，至少要可以自定义背景色和字体，白背景看得眼睛好痛。而且字体也太小了，也不支持html/css的格式化。</li>
<li>最近手贱<code>rm -rf /</code>了一下。。。这种事我一直是当作段子来看的啊，没想到居然会发生在我身上。看来不能高估自己的san值。</li>
<li>无论什么语言、框架、工具，甚至无论什么事情，生活中/工作上，<strong>想象力</strong>都是最重要的，它决定了一个人的上限。做出有想象力的作品是很难的，只会循规蹈矩很难有出路。为啥会有这个感慨呢，因为组里有个树莓派，在想能拿来干点啥。。。google的过程中惊叹于各种神奇的作品。有时候脑洞大点真不是坏事。</li>
<li>最近稍微看了些<a href="https://zh.wikipedia.org/wiki/%E5%B0%BD%E7%AE%A1%E5%8E%BB%E5%81%9A" target="_blank" rel="external">GTD</a>相关的东西，在想要不要搞下“规范的”时间管理。这种方法论呢，<a href="http://www.mifengtd.cn/articles/do-you-need-a-complete-gtd.html" target="_blank" rel="external">没有定式</a>，不要沦为单纯的使用工具，适合自己的才是最好的。</li>
</ol>
]]></content>
    <summary type="html">
    <![CDATA[<p>又一次不务正业，研究了下微信小程序。</p>
<p>其实微信小程序已经火了好久了。几个月之前内测的时候，很多人就迫不及待去研究，各种破解资料、教程满天飞。公司内部也专门组织人去学习了下。<br>可惜当时我事情比较多，只是打了个酱油，等公测开始才真正去研究了下。<a href="https://github.com/jiangxy/wxapplet-canvas-demo" target="_blank" rel="external">写了个简单的小游戏</a>，算是对小程序有了个大概的了解。话说最好的学习方式果然还是实践。古人云：“纸上得来终觉浅，须知此事要躬行”。</p>
<p><strong>本文不是一个教程</strong>，只是总结下学习过程中的感想。</p>
]]>
    
    </summary>
    
      <category term="小程序" scheme="http://jxy.me/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[壹〇二四]]></title>
    <link href="http://jxy.me/2016/10/24/1024-festival/"/>
    <id>http://jxy.me/2016/10/24/1024-festival/</id>
    <published>2016-10-24T14:22:19.000Z</published>
    <updated>2016-11-06T09:03:39.000Z</updated>
    <content type="html"><![CDATA[<p>据说今天（2016-10-24）是程序员的节日？不过没啥过节的气氛嘛，大家还是该干啥干啥。<br>懒得想题目了，随便借用一下。题目这种东西嘛，不是很重要，反正是无主题随意吐槽。<br>不过1024是开篇的日子，等写完估计是十一月了。。。</p>
<a id="more"></a>
<h1 id="PM小课堂？">PM小课堂？</h1>
<p>最近在持续跟进一些项目，每天都是鸡飞狗跳的，搞的自己心力交瘁。。。过程中发现了各种槽点，不吐不快。我的观点未必是对的，只是总结下自己的想法。</p>
<p>首先明确项目中的几个角色：需求方，就是使唤你干活的人，上至CEO下至运营都可能是需求方；产品，通称PD（似乎这是阿里系的叫法），负责细化需求，把控产品走向，通俗点说就是写PRD的人；技术，根据PRD出技术方案并实现，一般每个项目会有一个技术方面的负责人，通称PM，负责推进整个项目的进度。注意这里的PM指的是“项目管理”，而不是“产品经理”。其他角色还有交互、视觉、测试、运维之类的，暂时按下不表，因为他们一般不是专职针对某个项目，不会背相应的KPI，而只是给项目“打工”。<br>简单点来看，项目的进行过程就是需求方 vs PD vs PM的三方角力。</p>
<p>PM一般是开发来做，因为能比较好的把控全局。如果让一个非技术的人来做PM，可能会比较惨，容易被人忽悠。。。PM不需要了解所有的技术细节，但至少要知道大概，整体的技术方案都需要他来把控。<br>PM的工作职责是啥？如果说的官方一点：把控开发进度/及时处理风险/和各方沟通/保证按时交付之类的。但这都是正确的废话，真实答案就是——啥都要干，PM的工作没有边界，只要是跟项目相关的，可能影响进度的，都需要你去搞定。要会编码，会催进度，更要会撕逼。于是我的日常大概是这样的：<br>“你这边进度怎么样啊，有没有啥问题”<br>“这里体验不好，修改下”<br>“这个数据还没好？我去找相关的人，明天给你搞好”<br>“这个改造的方案，我们一起找XX看下”<br>“用户说有bug？你在哪里我去看下”<br>每天跑来跑去，找各种各样的人，解决各种奇奇怪怪的杂事。任何问题都会找到我，用户有问题我要解答，其他系统接入我要去对接，视觉进度慢我要去催，联调有问题也要我想办法。。。很难有整块的时间去写代码，往往要等到下班了，没人找我了，才能写自己的代码。要不怎么说每天都是鸡飞狗跳呢。。。</p>
<p>为啥会这样？因为如果项目延期/失败了，锅一定是PM的，这个逃不掉。需求方都是大老爷，当然不能背锅；至于PD，要看项目的不同，有些项目中PD主导的比较多，也有些项目中PD就是个打工的，帮你写文档而已，所以PD可能背锅也可能不背；视觉/交互/测试也没有背锅的理由，人家只是帮忙的；只有PM的锅，是绝对逃不掉的。</p>
<p>所以PM确实要去操心各种琐碎的事情，因为这是PM的职责。我作为项目对外对内的唯一接口人，对外要保证各种问题/bug/需求得到及时处理，就算不能立刻搞好也要有个回复，给出时间点，有点类似运维。。。对内要保证成员工作进度，不至于block，解决他们的任何问题，保证技术上整个流程是通的。很多时候我要先有技术方案，然后告诉他们如何去做。如果技术方案不通，我要负责找各种人，想出可行的方案。</p>
<p>如果说项目是一艘航行的船，一般来说PD就像船长，要负责船在驶向正确的方向；而PM就像大副/管家，要负责船上的人往同一个方向划水，才能形成合力。不至于有的人向左有的人向右，船就只能原地打转。如果你身兼PD&amp;PM，那就呵呵了，只能自求多福。<br>而且不得不说，很多人都是不催就不会动的，完全没有自己是项目中一员的自觉，一副事不关己的态度。就好像船已经漏水，你拼命往岸边划，船上其他人却在一边吃瓜一边围观，间或给你叫个好加个游。。。</p>
<p>更悲催的是，往往你手上不会只有一个项目，而是多个项目并行。。。如何协调，如何排期，这就是考验你智商/情商的时刻了。这比研究hadoop难多了好嘛。</p>
<p>既然已经是PM了，职责已经逃不掉了，能不能让自己更舒服些呢？我胡乱总结了下：</p>
<ul>
<li>善用制度和工具。根据项目的不同，适当使用评审/周会/文档/jira/wiki等方法，但千万不要生搬硬套，要根据项目的特点。人越多的项目越需要各种制度的约束，如果只是几个人的小项目，制度反而会带来额外的负担。一般来说，定期的周会/周报就能解决很多问题了，要让大家养成定期总结、定期review的习惯，提需求/提问题/吐槽都尽量集中到周会上，而不要随时随地提，搞的很烦。</li>
<li>善用工具保证开发质量。其实这条和上一条差不多的，但是专门针对开发而言。比如单元测试、代码静态检查、code review、持续集成、完善的发布系统、遵循统一的git分支模型等，反正尽量不要出现低级bug。曾经有同学代码编译不过都敢提交，也是醉了；还有同学合并分支时，把另一个分支上的修改全部抛弃了。。。出现一些业务逻辑上的bug可以忍，这种低级bug不能忍。同样也是要根据项目的情况去选用的。其实这里有个悖论，就是“NB的个人 vs 完善的制度”。如果团队能力足够强，可能不需要额外的措施去保证质量。据说amazon里各种系统/制度非常完善，就是因为它里面新人开发很多？总之要自己权衡。</li>
<li>规划好自己的时间。因为会有各种各样的人“打上门来”，如果碰到问题立刻就去解决，很快就会疲于奔命。可以了解下<a href="http://www.jianshu.com/p/6355411392e6" target="_blank" rel="external">四象限法</a>，合理安排各个事情的优先级。我现在啊，就有点像是一个MQ，你有问题过来了，我会先ack一下，确认收到，然后消息暂存，但不保证什么时候去消费。。。现在就是一个异步处理，处理完成去调你的callback。。。另外，现在也越来越离不开便签之类的东西了，事情太多太杂，只凭脑子根本记不住。</li>
<li>拒绝二手需求，从我做起。啥叫二手需求？<strong>老板和PD说，我要A；PD和我说，老板要A、A+、AAA、∀、Å、Д。。。</strong>看着好像都是A，但已经偏离原始需求很多了，就是蜗牛跟天牛的区别。所以啊，千万别傻乎乎的直接开始编码了，要真正的坐下来和需求方聊下，看下真实的需求是什么样的。“这是老板要的”似乎就是尚方宝剑，也是一些PD的护身符，似乎这句话一说所有人都只能乖乖听话。。。但很可能只是拿着鸡毛当令箭罢了。</li>
<li>拒绝一句话需求。其实是拒绝所有模糊的需求。有运营跟我说，“这个页面我们要改下，突出商品属性”，然后就问我什么时候改好。我tm怎么知道你要怎么突出啊？字体加粗？改背景色？大图？多图？倒是说清楚啊。对于这种需求，只能拜拜了您呐。另外一种常见的需求就是“和XXX一样就可以了”，这tm也是个坑。以前电商很火热的时候，各个小公司都想做自己的电商网站。一问需求，“和淘宝一样就可以了”。。。</li>
<li>拒绝“伪需求”。其实很多需求都是需求方一拍脑袋就提出来的，三分钟热度而已。我见过很多，“这里帮我改一下吧，不改要死了啊”/“想想办法，必须要上的啊”/“不就这里加个图片么”，更有甚者甚至语带威胁，不改就如何如何。当初需求评审/视觉评审的时候怎么不说？我废了很多口舌去解释，他们翻来覆去就是那句话“要改，不改不行”。结果呢，放置一段时间再问，“那就下期再改吧”/“那就当bug修复吧”/“不改也可以”。。。</li>
<li>别怕闹事，该吵就要吵。PM是要代表项目组去和各方PK的，摩擦在所难免，尤其是涉及到和其他组的一些利益分割时。这种情况下，独善其身是不可能的，怎么办，只能硬着头皮上了啊。有句话怎么说的，“没事别找事，有事别怕事”。关键时刻要敢于邮件@所有人，拉更多的人进来一起撕，最后总会有个结果的。不过就像我之前说的，大多数人负责，就等于是没人负责。对项目整体而言未必是好事。</li>
<li>Keep Calm。PM可能会碰到很多让人气愤的事，你有时会忍不住想说去年买了个表，尤其是有些人一副居高临下的态度找到你，“老子怎么说你就怎么做我才是大爷”时。要么放置play，要么就只能撕起来了。但吵架只是一种态度而已，真为这种事生气就很傻了。NBA里的教练为啥会冲过去找裁判理论然后吃T？难道他真的生气？其实有很大的表演成分在里面，给裁判施加压力而已，表明态度“我很生气你丫判罚尺度注意点这tm是我主场”。PM同理，所以好的PM都是很腹黑的么。。。</li>
<li>明确自己的边界。知道哪些是自己的系统该做的，哪些是应该交给其他外部系统的；哪些是现在该做的，哪些可以留到下期。不能所有的需求不加思索就吞下。是否会破坏系统已有的结构？是否有特殊的业务逻辑在里面？</li>
<li>不要高估别人的下限，敬nc而远之。和这种人辩论一定会输，因为他们听不进去你说的任何话，只会翻来覆去讲自己的道理，复读机一样，任何摆事实讲证据的逻辑都是无效的。但是他们又会崇拜“权威”，甚至可以说是跪舔。。。所以不得不撕的话，一定要拉上他们老大。。。</li>
</ul>
<p>PM是个挺锻炼人的活，你会见到形形色色的人，碰到各种各样的事。<br>最大的收获是，我以为我情商已经够低了，没想到有人比我还低，顿时觉得生活又充满了希望啊。。。</p>
<h1 id="50th_&amp;_100000">50th &amp; 100000</h1>
<p>写这篇文字的时候，突然意识到这是我的第50篇blog。于是写了段程序统计下字数（只计算汉字），正好是10w字多一点，算上这篇估计有11w了吧。能坚持下来敲这么多文字，还是有点开心的。</p>
<p>其实写blog的初衷很简单，我希望把工作相关的事情都沉淀下来，回头看自己过去几年的时候，不至于觉得虚度光阴。而且一些技术点记录下来，也方便自己以后查找。<br>大多数文章，都是以前在网易的时候写的，很多是纯技术向的。那时候工作内容比较“单一”，安心研究hadoop就好了，顺带着各种大数据技术都了解了下，真的是两耳不闻窗外事。在网易的几年最大的收获是培养了一种技术上的“素养”或者说“习惯”、“思维方式”，这种感觉很难说的清。离职后，各种杂事多了起来，更新频率大大降低，而且各种吐槽也多了起来，越来越不正经。。。不过至少开阔了眼界，了解了各种业务上的东西。</p>
<p>偶然的机会，google到自己以前的文章，突然有点伤感。以前俺还是个严肃的人啊，写各种技术文章，厚着脸皮说质量还算可以吧。。。也能帮到一些人。但最近越来越有玩票的感觉，其实有点怕。。。技术这种东西呢，不进则退。如果不能在某个领域深耕，那个人价值在哪里？在不同的业务逻辑上一遍遍重复着同样的技术？那还有啥意思啊。最近面试了几个工作7、8年的人就是这种情况，毫无悬念的挂掉了。。。<br>旁边搜索的同学在研究ES，我也很口水啊。身不能至心向往之。</p>
<p>如何从繁杂的日常事务中抽身去研究一些东西，真是一个很难的问题，精力终究有限。而且我所说的研究不是那种跑个docker，搭个kafka就好像自己掌握了宇宙真理一样，然后到处吹逼，这些人没听说过奈何姓万的故事么？研究一门技术/工具/系统，最重要的是掌握原理，其次是使用场景、可能遇到的问题，能深入到代码最好。有些同学天天git push/git pull就号称“熟练使用git”，分支怎么合并都不知道好意思么。。。<br>而且仅凭个人兴趣的研究和公司层面所支持的研究，能达到的高度是完全不同的。就像我如果业余去研究下Lucene，也能大概了解原理，说不定还能看看源码，但应用起来肯定还是一堆坑，跟搜索那边天天搞这玩意的同学根本没法比。又比如hadoop，如果不是有一线的运维经验，很多坑你根本想象不到。坑这种东西，不是看代码能看出来的。</p>
<p>不过反过来想想，“业务”和“技术”根本不是对立的，二者应该是相互驱动的。良性的循环是业务需求催动技术水平的提升，技术的提升又反过来给业务更多想象空间，这种循环的典型就是bigdata；恶性的循环就是业务半死不活，技术毫无成长。如何进入良性循环才是每个人都要思考的。<br>当然有人说可以纯搞技术，不沾任何业务逻辑，比如DBA、比如运维、比如各种中间件。如果能有这种外部环境，公司能提供一个良好的氛围/待遇/考核制度，我举双手双脚赞成，其实我tm也想这样。。。如果可以安心搞研究还照样发薪水，每天的工作就是往开源社区提交feature/patch，公司鼓励你成为contributor/committer，根据你对社区的贡献设定KPI，我可能立刻就会跑过去投简历。。。但就我看到的公司而言，大都是业务驱动的。哪个部门手里有业务，哪个部门就有发言权，就更强势，其他部门负责支撑性的工作，也负责背锅。。。这跟公司的“基因”真的关系很大，是业务导向还是技术导向。<br>而且就算某些公司能提供这种环境，这种赔本赚吆喝的事又能持续多久呢？毕竟公司是要赚钱才能活下来的，有多少公司愿意养着一个不产生收益，纯研究性质的部门？就算养了这样一个部门，这个部门的待遇和其他部门是一样的么？前段时间有个<a href="https://www.zhihu.com/question/27598510" target="_blank" rel="external">知乎帖子</a>也讨论过这个问题。</p>
<p>好像扯远了。。。本来是在说个人成长。但个人成长这个问题真的很复杂，我上面发了这么多牢骚，其实也没有答案，只能不断摸索吧。</p>
<p>anyway，至少我还没有停止思考。<br>但行好事，<del>莫问</del>略问前程。</p>
<h1 id="又是校招季">又是校招季</h1>
<p>赵忠祥老师：<del>春天</del>秋天到了，万物<del>复苏</del>了，这是个校招的季节。。。</p>
<p>前段时间校招如火如荼，各种改卷子、面试，曾经一天之内电面了7个同学，稍微总结下感想。</p>
<p>总的印象就是：好的很难招，跟大公司抢人是很难的；差一点的呢，老大又不愿意招。。。导致成果寥寥。不过这不是我要说的重点，我只是总结下笔试、面试的感想。</p>
<p>校招么，当然不会很看重项目经验，毕竟都是刚毕业。最看重的是基础，各种基础知识必须要扎实，算法/数据结构/java之类的，如果能有工程经验更好；其次是学习态度，是否积极主动，是否有探究精神，是否能快速的学会新知识，这是判断新人潜力的重要标准。扎实的基础+积极的态度，即使面对陌生的环境和工作也能很快适应，这样的新人才值得培养。</p>
<p>对于笔试，只要尽自己所能就可以了。有题目答不出是正常的，笔试题最大的目的是区分不同的档次，所以题目都会很有层次。如果你所有题都能答出来反而是有问题的。。。但答不出来最好也写写想法，关键是要有自己的思考。对于有些交白卷就夹个简历上来的同学，我也只能呵呵了。。。<br>面试也是一样的道理，注重基础。注意自己简历上的每一个字，都必须是真实可信的，面试官不一定会从哪里开始问。如果有什么虚假/夸大的经历/项目被发现，一般直接gg了。</p>
<p>有些同学啊，面试时总是喜欢扯各种业务逻辑，把实习时参与的一些系统的整个业务给我描述了一遍，但其实我根本不关心。。。我关心的是你在这个系统里扮演什么角色，做了什么事情，用了什么技术，碰到什么问题。如果能讲清楚技术细节，为什么用这些技术，就更好了。<br>其实，社招时很多人也有这个毛病，描述一大堆业务逻辑。确实业务很庞大很复杂，但毛线用都没，问起技术实现和细节还是一无所知。我只能认为那是心虚的表现了。。。<br>可能跟我个人有关吧，我更关注对技术本身的理解和实践，抛开那一堆业务逻辑。</p>
<p>还有些同学总是举着一块挡箭牌，或者叫遮羞布，“我是做算法的”。因为是做算法的，所以可以不会java/没用过多线程/不会spring/没听说过maven/不会git/工程经验0。我说搞算法的hadoop/spark总应该知道吧，要不咱们聊聊mapreduce？回答是：不知道，只会用Caffe。。。研究机器学习/深度学习/神经网络固然高大上，但一些基础的开发知识还是要知道的吧。“算法”和“工程”从来都不是对立的，不知道为什么很多人觉得只能取其一。<br>社招中也有这种情况，只不过挡箭牌变成了“我是做业务开发的”，所以不了解分布式/大数据/高并发/Linux/运维是可以的。确实有些人经手的系统比较简单，这些技术也用不到，但你要去学习啊，要能看到远方啊，看到系统规模变大会发生哪些问题，瓶颈在哪里。有句话说的好，“兜揣2块钱，心怀500万”，我觉得这种心态用在学习技术上正合适，虽然夸张了些。。。</p>
<p>另外还见过一些同学，各种项目经验，还有自己创业好几次的，甚至都融到天使轮，但问起基础来还是一团糟。就像我之前说的，校招不会太看中你的经验，关键还是基础，简历上的项目经验/实习经历再光鲜又有何用。</p>
<p>最后一个感想：面试也是挺累的。。。不比写代码简单多少。。。</p>
<h1 id="KPI？">KPI？</h1>
<p>起因是看了zhihu<a href="https://www.zhihu.com/question/51142219" target="_blank" rel="external">一个帖子</a>。</p>
<p>因为最近面试比较多，往往会涉及到一个给面试者定级别的问题。当然我给的级别不是最终的，仅供参考，最终结果还是老大说了算，只是用于评估面试者一个大概的能力范围。那如何区分不同的级别呢？其实我的标准很简单：</p>
<ul>
<li>1档：需要被告诉做什么事，并且指导怎么做。</li>
<li>2档：告诉做什么事就可以，不用什么指导，最多code review下。</li>
<li>3档：能独立维护一个系统，自己去优化并对接需求。</li>
<li>4档：能从无到有做一个系统出来，担任项目PM。并且能做些工作之外的事，扩展技术深度和广度。</li>
<li>5档：能自己找到需求，从无到有做活一块业务。在某些技术领域能有深耕。</li>
</ul>
<p>当然这是个很粗略的分类，再高的级别我也应该碰不到了。。。关键字：独立、从无到有。<br>各个级别之间也不是泾渭分明的，边界不是特别清晰，也不是像打怪一样必须要一级一级上升。每个级别，除了有技术上的要求，更重要的是对各种“软实力”的要求，比如沟通/协调/组织/眼界。相对而言，技术水平的提升是最容易的，“软实力”的提升才是最难的，要看自身情况。<br>那个帖子里有句话说的很好：“很多中国教育的方式让人过度注重 technical，比较有可能忽略了其它领域”。</p>
<p>评级和KPI一直是个比较敏感的话题，也被人诟病很多，确实这种制度在执行时有很多问题，搞得大家怨声载道。。。不过我感觉这不能怪制度本身，就像minzhu一样。。。印度和美国都是minzhu，为啥印度的minzhu一直被人骂，美国的就一直被夸？制度是死的，执行制度的是人，是人就可能有偏差，可能有漏洞，有猫腻。</p>
<p>对于大公司而言，貌似还真的少不了KPI，因为没有更好的管理方式。大公司追求的已经不是快速扩张了，而是平稳发展。KPI就像是给马套上缰绳，确实平稳了，容易驾驭了，但也失去了冲劲和野性，从赛马变成了拉车的马。总之是把双刃剑，是否用/怎么用，都要视实际情况而定。<br>不过KPI制定的合不合理，执行的又如何，是否会被某些人滥用，就是另一回事了。<br>最糟糕的情况就是：做的越多，错的越多，导致大家都畏首畏尾，小心翼翼的维持现状。</p>
<p>关于升级再提一句，其实升级是个很玄学的事情，很多时候也要看运气，没人能告诉你怎么做。大多是挑战一些难度很大的事，不成功则成仁。。。就算这件事难度上达到了级别，但做不成也不算，不会有所谓的“苦劳”。</p>
<h1 id="杂七杂八的研究">杂七杂八的研究</h1>
<p><a href="https://www.v2ex.com/t/310767" target="_blank" rel="external">在 2016 年学 JavaScript 是一种什么样的体验？</a>：前端领域吐槽的集大成者。印象深刻的是一些评论：“jquery: 你大爷永远是你大爷”，“前端现在是面向issues编程”。。。<br>对二手前端而言嘛，纯粹是吃瓜看戏。如果一定要我在js框架中站队，我当然会选react，其次是vue，最次是angular。</p>
<p><a href="http://www.infoq.com/cn/articles/urls-development-history-domain-name-protocol-and-port" target="_blank" rel="external">URL的发展历史：域名、协议和端口</a>：科普考古文，以前也研究过DNS，觉得挺好玩。</p>
<p><a href="https://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html" target="_blank" rel="external">Tomcat 7 Class Loader HOW-TO</a>：起因是一个同学问我怎么看java程序启动后加载了哪些jar，我说<code>ps -ef</code>看java进程的classpath参数，后来发现这招对tomcat完全不灵。。。</p>
<p><a href="http://www.infoq.com/cn/articles/mvc-web-architecture-polymorphism" target="_blank" rel="external">多形态MVC式Web架构的分类</a>：MVC已经是一个有些混乱的概念了，别纠结概念，能解决问题就好。</p>
<p><a href="https://www.patreon.com/" target="_blank" rel="external">Patreon</a>：一个好玩的网站，可以让各种创意工作者得到捐助。我只知道vue的作者在上面募捐。</p>
<p>前端时间研究ML时，看了斯坦福的公开课，顺便也整理下常见的在线教育平台。对我来说，选择平台最重要的因素是看视频时能不能加速。。。比如2倍速播放。毕竟视频太长，很浪费时间，对于一些熟悉的领域，即使2x语速也完全能理解。</p>
<ul>
<li><a href="https://www.coursera.org/" target="_blank" rel="external">Coursera</a>：MOOC的鼻祖，自带Andrew Ng的大神光环，课程丰富，可惜国内访问会比较慢。视频支持加速。</li>
<li><a href="http://www.imooc.com" target="_blank" rel="external">慕课网</a>：似乎是专门针对程序员的，内容很丰富。视频支持加速。</li>
<li>老东家的<a href="http://study.163.com" target="_blank" rel="external">云课堂</a>和<a href="http://open.163.com" target="_blank" rel="external">公开课</a>：良心出品，各种类型的课程都有。可惜现在云课堂部分课程要收费，而且二者都不支持视频加速。</li>
<li><a href="http://www.icourse163.org" target="_blank" rel="external">中国大学MOOC</a>：似乎是官方背景的机构和网易公开课联手推出的，都是各种大学中的课程。支持视频加速。</li>
<li><a href="http://www.duobei.com" target="_blank" rel="external">多贝网</a>：界面舒服，内容丰富，尤其各种互动印象深刻。不支持视频加速。</li>
<li><a href="http://www.ted.com" target="_blank" rel="external">TED</a>：也不用多解释什么了，名声在外。不支持视频加速。</li>
<li><a href="http://www.hubwiz.com" target="_blank" rel="external">汇智网</a>：似乎用户不多，课程也比较少，没有视频教学。但我很喜欢这种偏实践的教学方式。</li>
</ul>
]]></content>
    <summary type="html">
    <![CDATA[<p>据说今天（2016-10-24）是程序员的节日？不过没啥过节的气氛嘛，大家还是该干啥干啥。<br>懒得想题目了，随便借用一下。题目这种东西嘛，不是很重要，反正是无主题随意吐槽。<br>不过1024是开篇的日子，等写完估计是十一月了。。。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Docker与虚拟化]]></title>
    <link href="http://jxy.me/2016/10/02/docker-for-mac/"/>
    <id>http://jxy.me/2016/10/02/docker-for-mac/</id>
    <published>2016-10-02T11:32:32.000Z</published>
    <updated>2016-10-05T05:09:31.000Z</updated>
    <content type="html"><![CDATA[<p>其实docker也火了好久了。<a href="/2015/09/04/some-messy-thought/">一年之前</a>我就想试下，结果在mac上安装太麻烦了，由于不支持<a href="https://linuxcontainers.org/" target="_blank" rel="external">LXC</a>，还要搞个VirtualBox的虚拟机过渡，感觉很不爽。既然要用虚拟机，不如直接选择<a href="https://www.vagrantup.com/" target="_blank" rel="external">Vagrant</a>。<br>但最近<a href="https://docs.docker.com/docker-for-mac/" target="_blank" rel="external">Docker for Mac</a>发布了，据说不再需要VirtualBox了，赶紧来试下，顺便总结下自己对于虚拟化的一些理解。</p>
<a id="more"></a>
<h1 id="Docker_for_Mac">Docker for Mac</h1>
<p>首先要知道docker中的一些基本概念：</p>
<ul>
<li>docker daemon：用于管理容器的进程，对外暴露接口</li>
<li>docker client：用户端的命令行工具，可以通过unix socket（unix:///var/run/docker.sock）或tcp协议与daemon进程通信。与daemon进程其实是一对多的关系。</li>
<li>image：镜像，就是一个预先定义好的环境，可以直接载入容器中运行。只读，由Dockerfile定义。Dockerfile某种意义上类似linode中的的<a href="https://www.linode.com/stackscripts" target="_blank" rel="external">StackScripts</a>，都是一堆命令的集合，在环境初始化的时候执行。</li>
<li>registry：仓库，存放各种镜像，有点类似maven中的仓库概念。docker提供一个官方仓库<a href="https://hub.docker.com/" target="_blank" rel="external">Docker Hub</a>，也可以自己搭建私服。</li>
</ul>
<p>更多的docker相关知识还是看<a href="https://docs.docker.com/" target="_blank" rel="external">官方文档</a>，各种第三方教程可能比较落后，毕竟docker现在版本迭代非常快。</p>
<p>由于历史原因，要在mac上使用docker，有几种选择：</p>
<ul>
<li><a href="http://boot2docker.io/" target="_blank" rel="external">boot2docker</a>：基本原理是自带一个最小化的VirtualBox的Linux镜像，在虚拟机中运行docker daemon，然后本机的docker client通过网络与deamon交互。这是一个<a href="https://github.com/boot2docker/osx-installer/releases" target="_blank" rel="external">已经废弃</a>的项目，不要被一些老的文章误导。</li>
<li><a href="https://docs.docker.com/toolbox/overview/" target="_blank" rel="external">Docker Toolbox</a>：boot2docker的继任者，<a href="https://blog.docker.com/2015/08/docker-toolbox/" target="_blank" rel="external">去年8月发布的</a>。基本原理是一样的，都是利用了一个VirtualBox的虚拟机，但Docker Toolbox同时封装了其他一些常用的工具，让使用更方便。也是官方推荐的安装方式之一。</li>
<li><a href="https://docs.docker.com/docker-for-mac/" target="_blank" rel="external">Docker for Mac</a>：<a href="https://blog.docker.com/2016/03/docker-for-mac-windows-beta/" target="_blank" rel="external">今年6月发布的</a>最新工具，解决了Docker Toolbox的很多痛点，用起来更爽了。基本原理还是内嵌一个虚拟机，但是从VirtualBox换成了xhyve（这个我在文末会讲），而且对虚拟机做了很多定制化，反正就是很好很强大就对了。对于新老虚拟机的一些区别可以参考<a href="https://docs.docker.com/docker-for-mac/faqs/#/what-is-hyperkit" target="_blank" rel="external">FAQ</a>。</li>
</ul>
<p>Docker for Mac虽然好用，但是有一些限制条件：</p>
<ul>
<li>mac必须是2010年之后的型号。其实就是CPU要支持Intel VT-x。</li>
<li>系统10.10.3以上。我正好是10.10.3 Yosemite，但安装之后提醒我10.10.3下有一个已知bug，可能吃满CPU，目前只能重启Docker for Mac来解决。</li>
<li>至少4GB内存。</li>
<li>系统中不能安装有VirtualBox 4.3.30之前的版本。我之前为了用Vagrant，正好安装了VirtualBox 4.3.30。用<code>brew cask</code>安装的，显示为<code>virtualbox4330101610</code>。不过也没发现啥问题。</li>
</ul>
<p>能满足这些条件的话，尽量用Docker for Mac，否则只能用Docker Toolbox了。关于二者的一些区别，可以参考<a href="https://docs.docker.com/docker-for-mac/docker-toolbox/" target="_blank" rel="external">官方文档</a>。</p>
<p>Docker for Mac安装过程还是挺简单的，和安装普通的mac应用一样，对着<a href="https://docs.docker.com/docker-for-mac/" target="_blank" rel="external">官方文档</a>一步步来就可以了，如果最后能启动一个nginx的容器，正常访问<code>http://localhost</code>的话，就算成功了。说下我碰到的一些问题：</p>
<ol>
<li>拉取nginx镜像的时候，死活拉不下来。似乎我的DNS被污染了，一直报错<code>dial tcp: lookup index.docker.io on 192.168.65.10:53: no such host</code>。<code>index.docker.io</code>这个域名也是ping不通，似乎使用了AWS的服务，被墙了？于是满世界找国内的镜像，最后使用了<a href="https://www.daocloud.io/mirror" target="_blank" rel="external">DaoCloud</a>，它的文档还挺新的，已经有针对Docker for Mac的配置方法了。</li>
<li>配置好DaoCloud的镜像后，居然还是报错<code>dial tcp: lookup dao-booster.daocloud.io on 192.168.65.10:53: no such host.</code>，但是<code>dao-booster.daocloud.io</code>是可以ping通的，真是见鬼。无奈在<code>/etc/hosts</code>里手动写死ping到的IP，加了两条记录<code>106.75.18.185 m.daocloud.io</code>和<code>183.131.208.55 dao-booster.daocloud.io</code>，终于正常了。</li>
<li>如果点击Docker for Mac菜单栏中的<code>Open Kitematic</code>（<a href="https://kitematic.com/" target="_blank" rel="external">Kitematic</a>是一个管理容器的图形界面工具，很好用，如果你用Docker Toolbox的话会自动安装，但用Docker for Mac的话就要自己下载），它会给你一个<a href="https://download.docker.com/kitematic/Kitematic-Mac.zip" target="_blank" rel="external">下载链接</a>，并跟你说下载解压之后放到<code>/Applications</code>下就可以使用了。结果我下载后解压出一个<code>Kitematic (Beta).app</code>，居然打不开，mac会提示说文件已经损坏。。。google了一下应该是个bug，解决方法见<a href="https://github.com/docker/kitematic/issues/1637" target="_blank" rel="external">这里</a>。</li>
</ol>
<p>Docker for Mac安装成功后，命令行会多几个工具：</p>
<ul>
<li>docker：其实就是docker client</li>
<li><a href="https://docs.docker.com/compose/overview/" target="_blank" rel="external">docker-compose</a>：似乎是用于部署分布式服务的，同时启动多个容器之类的，没用过</li>
<li><a href="https://docs.docker.com/machine/overview/" target="_blank" rel="external">docker-machine</a>：非Linux平台如果想用docker都要用到虚拟机，这个工具就是用来管理虚拟机的。以前boot2docker也会自带一个类似的命令<code>boot2vm</code>（好像是这个名字），功能上差不多。但Docker for Mac中应该用不到这个命令了，除非你想蛋疼的搞多个虚拟机，或者让Docker Toolbox与Docker for Mac<a href="https://docs.docker.com/docker-for-mac/docker-toolbox/#/docker-toolbox-and-docker-for-mac-coexistence" target="_blank" rel="external">共存</a>。这个命令可能是为了将虚拟机从Docker Toolbox迁移到Docker for Mac才存在的。</li>
</ul>
<p>总的来说，Docker for Mac已经完善很多了，用起来很接近原生的体验了。以前用Docker Toolbox很蛋疼的一点就是网络问题，借用官方的一张图：<br><img src="docker-toolbox.png" alt=""><br>首先我必须要设置一堆环境变量，才能让<code>docker</code>命令知道去连接那个虚拟机。其次如果我部署了一个nginx容器，我要用<code>http://12.34.56.78</code>才能访问（12.34.56.78是虚拟机的IP），而不能直接用<code>http://localhost</code>访问。<br>反正就是处处都会透出“虚拟机”的存在，很蛋疼。那我还不如直接去用虚拟机。。。<br>Docker for Mac就好很多了，让你感受不到虚拟机，像原生docker一样直接使用。</p>
<p>最后截图留念：<br><img src="dockersay.png" alt=""></p>
<h1 id="虚拟化">虚拟化</h1>
<p>虚拟化（Virtualization）实在是一个很学术的词，并非我们通常意义上所理解的虚拟机，摘录一段<a href="https://zh.wikipedia.org/wiki/%E8%99%9B%E6%93%AC%E5%8C%96" target="_blank" rel="external">Wiki</a>上的说明：</p>
<blockquote>虚拟化是一个广义的术语，对于不同的人来说可能意味着不同的东西，这要取决他们所处的环境。在计算机科学领域中，虚拟化代表着对计算资源的抽象，而不仅仅局限于虚拟机的概念。例如对物理内存的抽象，产生了虚拟内存技术，使得应用程序认为其自身拥有连续可用的地址空间（Address Space），而实际上，应用程序的代码和数据可能是被分隔成多个碎片页或段），甚至被交换到磁盘、闪存等外部存储器上，即使物理内存不足，应用程序也能顺利执行。</blockquote>

<p>从这个角度来说，只要能提供一个抽象层并屏蔽底层细节的技术/工具，都可以被算作“虚拟化”的范畴？“进程”是不是虚拟化？JVM呢？以前的各种虚拟光驱（谁还记得Alcohol 120%）也算是虚拟化吧。不过不要纠结这些细节，学术之争不影响使用。能抓到耗子就是好猫。<br>大多数时候我们谈论的虚拟化，还是特指虚拟机。</p>
<h2 id="虚拟机">虚拟机</h2>
<p>我对虚拟机最早的接触，就是Win下用Vmware Workstation 4安装红旗Linux。。。这货现在都没人记得了吧，安装光盘好像还是买什么杂志送的。后来陆续用过VirtualPC、VirtualBox、Vmware Fusion。以前用linode的时候，也了解一些<a href="https://zh.wikipedia.org/wiki/Xen" target="_blank" rel="external">Xen</a>、<a href="https://zh.wikipedia.org/zh-cn/OpenVZ" target="_blank" rel="external">OpenVZ</a>之类的概念。但一直没有系统的研究过，趁着研究docker的机会一起整理下。</p>
<p>首先要明确的一个概念，就是<a href="https://zh.wikipedia.org/wiki/Hypervisor" target="_blank" rel="external">Hypervisor</a>，又叫做VMM（Virtual Machine Monitor）。通俗的说，就是一个创建、运行、管理虚拟机的程序。上文提到的各种软件，无论VirtualBox、Vmware，还是Xen、OpenVZ，都可以算作Hypervisor。Hypervisor这个词只是限定了软件的功能，而没有限定实现方式。就像所谓的“文本编辑器”一样，可以有多种实现，vim/emacs/nano/sublime之类的。根据Hypervisor的不同实现方式，大致可以分为2类：</p>
<ul>
<li>Type 1：Hypervisor直接运行在裸硬件上。从某种意义上来说，这种Hypervisor就是一个微型的操作系统，要自己跟各种硬件打交道，处理上层OS的请求。典型的就是Xen。</li>
<li>Type 2：Hypervisor运行在OS之上，只是OS中一个普通的应用程序。这个很好理解，我们常用的VirtualBox，Vmware Workstation都是这种。</li>
</ul>
<p>一般来说，Type1型的效率会更高，毕竟指令可以直接到达硬件，而Type2型中还要经过“GuestOS -&gt; Hypervisor -&gt; HostOS”的一个“转译”过程。但Type2可以利用宿主OS的内存管理/进程调度/资源管理等，实现上应该会简单些。</p>
<p>另一个要理解的概念，就是“全虚拟化”和“半虚拟化”。个人理解：</p>
<ul>
<li>全虚拟化（Full Virtualization）：就是通常意义的虚拟化，所有GuestOS中的操作，都由Hypervisor处理。GuestOS不用做任何修改，也不知道自己是运行在虚拟机中的。</li>
<li>半虚拟化（Partial Virtualization）：在全虚拟化的基础上，与GuestOS做了一些“约定”。GuestOS知道自己运行在虚拟机中，一些特殊的操作，可以绕过Hypervisor直接执行（需要和Hypervisor约定好，不过这是我猜的）。这样可以大幅度提高效率，代价就是GuestOS必须修改内核，才能对虚拟机做特定的优化，应该叫做“虚拟机专用版OS”。</li>
</ul>
<p>有人说全虚拟化/半虚拟化的区别在于是“用软件模拟所有硬件”还是“用特定硬件辅助模拟（比如<a href="https://zh.wikipedia.org/wiki/X86%E8%99%9A%E6%8B%9F%E5%8C%96" target="_blank" rel="external">Intel VT-x</a>）”，我觉得是错的。如果硬件上提供了优化，确实能提高效率，但GuestOS本身还是不知道自己是运行在虚拟机中。这种技术在<a href="https://zh.wikipedia.org/wiki/%E7%A1%AC%E4%BB%B6%E8%99%9A%E6%8B%9F%E5%8C%96" target="_blank" rel="external">Wiki</a>中称作“硬件辅助虚拟化”（Hardware-Assisted Virtualization），很多人都会混淆这个概念。</p>
<p>一个典型的例子是在Vmware Workstation中安装虚拟机时，一般还要安装Vmware Tools，能大幅提高虚拟机的性能。我觉得这就是一种半虚拟化，让系统知道自己运行在Vmware虚拟机中，进而一些操作可以利用Vmware Tools去优化。当然你不装也可以，虚拟机也能正常用，就是用着卡点，很多硬件特性（比如3D加速）也不能用。</p>
<p>早期广泛使用的Hypervisor，大多是半虚拟化的，因为这样可以提高虚拟机的性能，比如Xen。在Xen上运行的GuestOS，必须使用特定版本的内核，根据不同的发行版还要自己编译，比较麻烦。由于协议的问题，也不能虚拟Windows，因为要修改Windows内核才行。后来随着CPU厂商逐步提供硬件级别的虚拟化支持（Intel VT-x、AMD-V），全虚拟化的成本大大降低，性能损失也可以接受了，而且GuestOS不需要做任何修改，逐渐成了主流。Xen也开始支持了硬件辅助的全虚拟化（<a href="https://wiki.xen.org/wiki/Xen_Project_Software_Overview#HVM" target="_blank" rel="external">Xen-HVM</a>），可以虚拟Windows了。可见全虚拟化才是大势所趋。</p>
<p>一个经常拿来和Xen对比的Hypervisor，就是<a href="https://zh.wikipedia.org/wiki/%E5%9F%BA%E4%BA%8E%E5%86%85%E6%A0%B8%E7%9A%84%E8%99%9A%E6%8B%9F%E6%9C%BA" target="_blank" rel="external">KVM</a>。KVM是基于硬件的完全虚拟化（补充一下，这里所谓的“硬件”，特指CPU，要支持Intel VT-x/AMD-V之类的指令集，目前只有CPU能提供硬件级别的虚拟化支持，其他硬件，硬盘/网卡之类的，暂时没有），但它的特殊之处在于它是集成在Linux内核之中的，通过Linux内核去做资源调度、硬件管理之类的，而它本身只负责管理虚拟机。<br>KVM很大程度上模糊了Type1和Type2的分类。它不是Linux内核之下硬件之上的一个“微型OS”，也不是Linux上的一个应用程序，应该说它就是Linux内核本身。这个Hypervisor不但可以像常规的Linux一样使用（这个Linux到底是HostOS还是GuestOS呢？鸡生蛋蛋生鸡。。。），还可以创建/管理虚拟机，而且像管理进程一样管理虚拟机。很神奇，有点波粒二象性的感觉。。。</p>
<p>关于Xen和KVM的对比，摘录几段话：</p>
<blockquote>从架构上讲，xen是自定制的hypervisor，对硬件的资源管理和调度，对虚拟机的生命周期管理等，都是从头开始写的。KVM是一个特殊的模块，Linux kernel加载此模块后，可以将Linux kernel 变成hypervisor，因为Linux kernel已经可以很好的实现对硬件资源的调度和管理，KVM只是实现了对虚拟机生命周期管理的相关工作。 KVM的初始版本只有4万行代码，相对于xen的几百万行代码显得非常简洁。<br></blockquote><br><blockquote>在以前没有vt-x的时候，xen致力于不依赖硬件，也不依赖qemu，一步一步把虚拟化做了起来<br>现在都是xen的历史包袱了</blockquote>

<p>另外提到KVM，就不得不提<a href="http://wiki.qemu.org/Main_Page" target="_blank" rel="external">QEMU</a>，这两个名词总是一起出现。QEMU本质上是一个用软件支持全虚拟化的Hypervisor，可以模拟各种CPU、各种硬件，本来跟KVM没啥关系。由于KVM本身只提供了CPU和内存的虚拟化，但一个完整的虚拟机还要有其他硬件；而且KVM只是一个内核模块，也需要一个用户空间的工具去调用。于是它利用了QEMU已有的代码（反正都是开源的嘛），改造下，成了所谓的<a href="http://wiki.qemu.org/KVM" target="_blank" rel="external">qemu-kvm</a>，这样才形成了完整的解决方案。</p>
<p>常见的开源Hypervisor就是Xen和KVM了。从现在的趋势来看，目测KVM会逐步取代Xen，毕竟是Linux官方支持的。<br>除了开源的Hypervisor，也有一些商业化Hypervisor，最出名的就是Vmware了吧，目前在企业市场主推的是<a href="http://www.vmware.com/products/esxi-and-esx.html" target="_blank" rel="external">ESXi</a>（话说它的产品线真是乱，vSphere/vCenter/ESX/ESXi）。此外还有微软的<a href="https://en.wikipedia.org/wiki/Hyper-V" target="_blank" rel="external">Hyper-V</a>（据说这货就是以前的VirtualPC？）。</p>
<p>最后再提一下<a href="http://libvirt.org/" target="_blank" rel="external">libvirt</a>。由于有这么多种Hypervisor，每种Hypervisor都有自己的虚拟机规范，大家觉得要管理这么多种Hypervisor好麻烦啊，于是有聪明人（好像是红帽？）想出了一个办法：提出一套统一的管理虚拟机的API和工具（比如命令行的virsh/图形界面的virt-manager），这样就可以对上层用户屏蔽各个Hypervisor的具体实现了。借用Wiki中的一张图：<br><img src="libvirt1.png" alt=""><br>或者这张图更清楚一些：<br><img src="libvirt2.png" alt=""></p>
<h2 id="容器">容器</h2>
<p>容器算是虚拟化中的另外一个潮流，跟Hypervisor的思路完全不同。</p>
<p>关于容器的讨论也有很多了，一些技术细节我也不打算复述，比虚拟机好懂多了。。。有人说容器是“轻量级的虚拟机”，个人感觉这种说法是有点问题的。容器本质上是一种资源隔离机制。普通的进程也可以做到资源隔离，只不过隔离的强度比较弱罢了。容器并没有什么新技术，只是利用Linux内核已有的一些机制，比如<a href="https://en.wikipedia.org/wiki/Linux_namespaces" target="_blank" rel="external">namespace</a>/<a href="https://zh.wikipedia.org/wiki/Cgroups" target="_blank" rel="external">cgroup</a>/<a href="https://en.wikipedia.org/wiki/Aufs" target="_blank" rel="external">AUFS</a>，做到了更好的资源隔离。这里所说的资源包括CPU/内存/IO/带宽等等。</p>
<p>容器相关的技术，其实可以追溯到<a href="https://zh.wikipedia.org/wiki/Chroot" target="_blank" rel="external">chroot</a>/<a href="https://zh.wikipedia.org/wiki/FreeBSD_jail" target="_blank" rel="external">FreeBSD jail</a>，更关注于“在已有的系统里创造出一个完全隔离的环境”，而不是“创建一个新的系统”，所以说跟Hypervisor是完全不同的思路。应该称作“沙盒”更合适些？也有点像用于引诱黑客的“蜜罐”。</p>
<p>容器不等于docker。虽然docker是现在最火的容器相关项目。其实docker只是一个容器管理的工具，而不是实现容器的关键技术。早期的docker可以认为就是<a href="https://linuxcontainers.org/" target="_blank" rel="external">LXC</a>之上的一层“皮”，但它能火起来是有原因的：</p>
<ol>
<li>做到极端好用。LXC虽然提供了命令行工具，但用起来<a href="https://www.ibm.com/developerworks/cn/linux/l-lxc-containers/" target="_blank" rel="external">很复杂</a>。docker极大的降低了使用门槛，一行命令就部署一个服务，一台机器上跑上百个容器，几年前你能想象嘛？</li>
<li>提出镜像&amp;仓库的概念。激发了广大人民群众的创造力，每个人都可以创建自己的docker image并分享给其他人。减少了很多重复造轮子的工作。事实证明，立足于群众才是取胜之道啊。</li>
<li>将系统与应用解耦，顺应了当前的DevOps潮流。而且向群众们灌输了“App-Centric”的观念，用docker串联开发/测试/交付，对于敏捷开发也有很大好处。</li>
</ol>
<p>所以，个人感觉，docker的胜利更像是用户体验的胜利，是营销/运营的胜利，而不是传统意义上的，技术突破的那种胜利。。。<br>docker应该说是很大程度上解放了运维的生产力吧，从侧面也能反应出运维有多么苦逼。。。另外docker也带了一波go语言的节奏？似乎现在的运维都非常喜欢go。</p>
<p>docker不等于LXC。虽然早期的docker只是LXC之上的一层“皮”，但随着docker的火爆，当然也要搞自己一点“原创”的东西（0.9版本之后），不能总是寄人篱下啊。借用一张图：<br><img src="docker.png" alt=""><br>通俗的说，docker要定义容器的标准，将底层实现都抽象化为libcontainer接口，和libvirt有些类似。到时候LXC只是一种可选的实现而已。不过现在还是只能依赖LXC。</p>
<p>自从容器技术火爆以来，很多人就在争论容器和Hypervisor孰优孰劣。很多人宣称<a href="http://dockone.io/article/1388" target="_blank" rel="external">容器将取代Hypervisor</a>。这里面有几句话讲的很好，摘录下：</p>
<p><blockquote>容器作为基础设施 vs 容器作为以应用程序为中心的打包与管理工具<br>容器具有两个视角：它们是基础设施（即“轻量级虚拟机”）？或是应用程序管理与配置系统？事实是，它们两者皆是。如果你是基础设施人员，你可能会将其视为前者，如果是开发人员，则可能会将其视为后者。</blockquote></p>
<p><blockquote>Hypervisor唯一的价值在于使用PV驱动支持多种操作系统，下一代数据中心并无此需求</blockquote><br>孰优孰劣我也不敢妄言，还是要看场景的。如果容器能满足需求就尽量容器吧，毕竟用着简单省事。如果我是运维，我宁愿牺牲些性能换取运维的便利性。<br>而且二者也不是非此即彼的关系，有一个<a href="http://www.infoq.com/cn/news/2015/06/Hyper-Hypervisor-Docker" target="_blank" rel="external">神奇的东西</a>，可以用Hypervisor去运行docker image，这个思路感觉挺好玩的。</p>
<p>不过想要用容器只能用新版本的Linux内核，导致可移植性比较差，而且Linux容器里总不可能跑Windows吧。。。Ubuntu里也不可能跑CentOS。这在某些情况下算是一个劣势。Hypervisor一般没这个问题。<br>macOS想用docker就很蛋疼。。。也许以后libcontainer更完善的话会好些。</p>
<h1 id="Term_Storm">Term Storm</h1>
<p>google的过程中找到了各种各样的专有名词，头都大了，总结下。<br>一些上文中已经解释过的名词就不再重复了。</p>
<ul>
<li><a href="https://zh.wikipedia.org/wiki/Chroot" target="_blank" rel="external">chroot</a>：我一直觉得chroot是个很神奇的命令，最早接触它是在linode挂掉需要Rescue Mode时。chroot可以形成一个简单的沙盒，算是容器概念的雏形了。</li>
<li><a href="https://zh.wikipedia.org/wiki/OpenVZ" target="_blank" rel="external">OpenVZ</a>：感觉上是一种Type2的Hypervisor，需要宿主OS做特定的修改。特色是“一台OpenVZ物理服务器的拥有者（root）可以看见所有虚拟环境的进程和文件”。反正当初买VPS的时候大家都推荐Xen的，说OpenVZ不好，超卖严重，而且受邻居影响很大。</li>
<li><a href="https://linuxcontainers.org/" target="_blank" rel="external">LXC</a>：linux container的简称，docker的基础。话说LXC要比docker早出现5年。。。</li>
<li><a href="https://www.openstack.org/" target="_blank" rel="external">OpenStack</a>：开源IaaS平台的事实标准。我没用过，感觉上是对虚拟机管理的一堆技术的整合，底层可以支持KVM/Xen/Vmware等各种虚拟机，但社区似乎推荐KVM。</li>
<li><a href="http://www.intel.com/content/www/us/en/virtualization/virtualization-technology/intel-virtualization-technology.html" target="_blank" rel="external">Intel VT-x</a>：若干年前很火热的概念。感觉上就是一套CPU指令集（类似SSE/MMX？），可以对CPU/内存的虚拟化提供硬件级别的支持，AMD对应的技术叫做AMD-V。VT-x只是提供了内存和CPU的虚拟化，提供IO虚拟化的技术称作VT-d，AMD对应的技术叫做AMD-Vi。但感觉VT-d说的较少，一般都是VT-x，因为软件支持较少，一般都是企业级软件才支持VT-d，比如Vmware ESXi。CPU是否支持VT-x/VT-d，可以去<a href="http://ark.intel.com" target="_blank" rel="external">Intel官方</a>查询。</li>
<li><a href="https://software.intel.com/en-us/articles/best-practices-for-paravirtualization-enhancements-from-intel-virtualization-technology-ept-and-vt-d" target="_blank" rel="external">Intel EPT</a>：实现内存虚拟的关键技术就是地址转换，因为GuestOS中的内存地址都是虚拟的，一般要维护一个虚拟地址到真实地址的映射关系。Intel EPT可以让这个转换过程用硬件去做，提高效率。AMD也有类似的技术。</li>
<li><a href="http://www.linux-kvm.org/page/Virtio" target="_blank" rel="external">Virtio</a>：针对KVM的半虚拟化驱动，有点类似Vmware Tools，只能用于Linux。有一些KVM的最佳实践中提到最好要安装Virtio。</li>
<li><a href="https://wiki.freebsd.org/bhyve" target="_blank" rel="external">bhyve</a>/<a href="https://github.com/mist64/xhyve" target="_blank" rel="external">xhyve</a>：bhyve是FreeBSD上的一个Hypervisor，感觉上是Type2，全虚拟化。特点在于体积小，轻量，而且支持很多新特性（VT-x/Virtio不用说了，居然支持VT-d）。xhyve是它在macOS上的移植版，也是Docker for Mac的核心，docker针对xhyve专门包装了一个组件叫做<a href="https://github.com/docker/HyperKit/" target="_blank" rel="external">HyperKit</a>。使用简介见<a href="http://www.vpsee.com/2015/06/mac-os-x-hypervisor-xhyve-based-on-bhyve/" target="_blank" rel="external">这里</a>。</li>
</ul>
<h1 id="参考资料">参考资料</h1>
<p><a href="http://virtual.51cto.com/art/201102/245920_all.htm" target="_blank" rel="external">Intel虚拟化技术-从VT-x到VT-d</a><br><a href="https://www.oschina.net/question/2548918_2149938" target="_blank" rel="external">KVM 虚拟化技术之Hypervisor 架构</a><br><a href="https://my.oschina.net/qefarmer/blog/386843" target="_blank" rel="external">qemu,kvm,qemu-kvm,xen,libvirt的区别</a><br><a href="http://linux.vbird.org/linux_enterprise/xen.php" target="_blank" rel="external">鸟哥的Xen教程</a><br><a href="https://www.ibm.com/developerworks/cn/linux/l-cn-vt/" target="_blank" rel="external">虚拟化技术漫谈</a><br><a href="http://www.ibm.com/developerworks/cn/linux/l-hypervisor/" target="_blank" rel="external">剖析 Linux hypervisor</a><br><a href="https://www.ibm.com/developerworks/cn/linux/l-using-kvm/" target="_blank" rel="external">使用 KVM 虚拟化技术</a><br><a href="http://www.cnblogs.com/xusongwei/archive/2012/07/30/2615592.html" target="_blank" rel="external">全虚拟化和半虚拟化的区别</a><br><a href="http://www.infoq.com/cn/articles/docker-container-management-libcontainer-depth-analysis" target="_blank" rel="external">Docker背后的容器管理——Libcontainer深度解析</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>其实docker也火了好久了。<a href="/2015/09/04/some-messy-thought/">一年之前</a>我就想试下，结果在mac上安装太麻烦了，由于不支持<a href="https://linuxcontainers.org/" target="_blank" rel="external">LXC</a>，还要搞个VirtualBox的虚拟机过渡，感觉很不爽。既然要用虚拟机，不如直接选择<a href="https://www.vagrantup.com/" target="_blank" rel="external">Vagrant</a>。<br>但最近<a href="https://docs.docker.com/docker-for-mac/" target="_blank" rel="external">Docker for Mac</a>发布了，据说不再需要VirtualBox了，赶紧来试下，顺便总结下自己对于虚拟化的一些理解。</p>
]]>
    
    </summary>
    
      <category term="docker" scheme="http://jxy.me/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[真・二手前端]]></title>
    <link href="http://jxy.me/2016/09/06/zhen-second-hand-qianduan/"/>
    <id>http://jxy.me/2016/09/06/zhen-second-hand-qianduan/</id>
    <published>2016-09-06T09:26:58.000Z</published>
    <updated>2016-09-15T16:44:48.000Z</updated>
    <content type="html"><![CDATA[<p><a href="/2015/09/21/second-hand-frontend/">《二手的前端开发》</a>续集，继续折腾，跳了React的大坑。。。</p>
<a id="more"></a>
<p>掐指一算，已经近3个月没更新了，再创新纪录，可喜可贺。<br>这段时间呢，可以说是忙，做着各种奇奇怪怪的事，跟各种各样的人打交道，写着各种“片段式”的代码。感觉很忙却又说不清自己在做什么。总是感觉时间很零碎，很久没有整块的时间去潜心做一件事了。这个有机会再单独写一篇去吐槽吧。<br>但也可以说是懒，毕竟还是有时间把《权利的游戏》1~5季追完。。。<br>人啊，总是会有“去tmd工作我什么都不想干就想宅着”的时候。</p>
<p>言归正传，本篇的主角是React。<a href="/2015/09/21/second-hand-frontend/">之前</a>我也搞过一点前端的工作，但是比较low，大多是jquery+bootstrap之类的东西，感觉就是在逆时代潮流而动啊。。。也听说过React，一直很口水，身不能至心向往之。尤其是<a href="https://facebook.github.io/react-native/" target="_blank" rel="external">React Native</a>出来之后，感觉就是“卧槽这也行”，颇有点当年java的“write once, run everywhere”的风范。不过java的这个牛皮是吹破了，react的口号也跟这个不太一样，“learn once, write everywhere”，后文再详细说。<br>正好之前有点空闲时间，新项目还没开始，于是就研究了下React和<a href="http://ant.design" target="_blank" rel="external">Ant Design</a>。初衷很简单，如果我能自己写前端，那做项目就不用求人了，前端资源真是很紧张。</p>
<p>一边学一边写，最后搞了<a href="https://github.com/jiangxy/react-antd-admin" target="_blank" rel="external">这么个东西</a>出来。本意是想简化下各种后台页面的开发，尽量做的通用一点，欢迎各种吐槽。</p>
<p>注意，本文<strong>不是一个React教程</strong>，只是总结下学习过程中一些个人的感想。React教程实在太多了，遍地都是。。。</p>
<h1 id="最佳实践？">最佳实践？</h1>
<p>首先我要抱怨下前端开发的现状，对于新人，对于二手前端，真是太不友好了。。。因为乱。</p>
<p>引用一张图片：<br><img src="/2016/09/06/zhen-second-hand-qianduan/1.png" alt=""></p>
<p>原图在<a href="http://fefe.jeroenheijmans.nl/" target="_blank" rel="external">这里</a>，列举了前端的大部分框架和相关的工具。反正我第一眼看到感觉就是“日居然要学这么多东西”。。。</p>
<p>之前我就觉得前端很乱。不是难，是乱。技术么，不管多难，只要肯下功夫总能学会的。但是工程化的标准、开发风格、开发框架之类的，前端一直没有一个“业界标准”，太多框架、太多工具可以选择。我用react，能实现需求；你用angular？也行啊；什么他要写原生js？貌似也可以。。。</p>
<p>一个很让人头疼的问题就是工程结构。一个典型的java工程，一般都会有<code>src/main/java</code>、<code>src/test/java</code>之类的目录放置源码，<code>target</code>目录放置构建结果，可以很清楚整个工程结构是什么样的，其他人接手也很简单。这就是maven提倡的“约定优于配置”的理念。但前端世界完全没有这种规范，编译后的目标目录，有人叫<code>build</code>，有人叫<code>dist</code>，也有<code>target</code>/<code>result</code>的。。。源码目录的结构更是一团糟，完全取决于开发者的个人兴趣。每次看别人的工程都很头疼。这时才感受到maven的可贵啊。</p>
<p>至于css/js/html/兼容性上的各种混乱，更是无力吐槽了。。。不过值得欣慰的是，至少比以前的洪荒时代好多了。</p>
<p>还有啊，为什么某个组件我升级个小版本就导致整个项目挂了啊，你们这版本号也太乱来了吧。。。</p>
<p>前端开发另一个怪现象就是很喜欢自己造轮子，即使有可用的工具也要自己重新造一套，我觉得大概有几个原因：</p>
<ul>
<li>确实现在前端生态圈还不完善，各个领域没有一个决定性的优胜者，只能说各擅胜场</li>
<li>因为没有标准，每个人都觉得自己掌握的才是宇宙真理。。。看别人的工具总是不爽，“文人相轻”比较严重，各种“派系”林立</li>
<li>js语言太灵活。一门语言如果表达力很强，就意味着不同的人写同样的功能，写出来的代码可能天差地别。而且你总觉得有更好的写法。。。所以说，太灵活的语言不适合协作，但适合炫技。。。</li>
</ul>
<p>不要说专业前端了，我这个二手前端都会一遍遍重构自己的代码。我会特意去用ES6的各种新特性，class/箭头函数之类的，不用不舒服斯基。说实话挺头疼的，尤其是对于强迫症。</p>
<p>唠叨了这么多，对“混乱”抱怨了这么久，所以，前端领域到底有没有所谓的“<strong>最佳实践</strong>”？<br>恐怕我无法回答这个问题。但在学习react的过程中，我总结了一些规则，仅供参考：</p>
<ul>
<li>抱着HTML5的大腿</li>
<li>写<a href="http://less.bootcss.com/" target="_blank" rel="external">less</a>，不要写原生css。话说css真是难，前端同学你们是怎么记住那么多样式的。我只能抄其他人的，或者找些开源的，再对着<a href="http://www.w3school.com.cn/cssref/index.asp" target="_blank" rel="external">css手册</a>一点点改，有时候调css能花上一天。。。</li>
<li>依赖管理都用<a href="https://www.npmjs.com/" target="_blank" rel="external">npm</a></li>
<li>js都用ES6的写法，但也要能看懂ES5。至于各种方言，CoffeeScript/TypeScript之类的，注定是历史的垃圾堆</li>
<li>一切以DOM为基础。虽然大家都说DOM慢，而且很多框架都会屏蔽DOM操作。但它是最底层的操作，理解之后有很多好处。不要上来就搞一堆框架专有的概念，没错我说的就是angular。。。</li>
<li>怎么熟悉DOM？先把<a href="https://jquery.com/" target="_blank" rel="external">jquery</a>/<a href="http://zeptojs.com/" target="_blank" rel="external">zepto</a>用熟吧少年</li>
<li><a href="http://babeljs.io/" target="_blank" rel="external">Babel</a>真是神器，可以让你提前使用ES6甚至ES7的特性。大部分特性是编译时的优化，可以认为是语法糖，但注意有些特性可能需要运行时的polyfill。</li>
<li><a href="https://webpack.github.io/" target="_blank" rel="external">Webpack</a>是React绝配，有各种神奇的loader和插件，后文详述</li>
<li>React全家桶（React+ <a href="https://github.com/ReactTraining/react-router" target="_blank" rel="external">React Router</a> + <a href="https://github.com/reactjs/redux" target="_blank" rel="external">Redux</a>）看情况是否采用。一般而言React Router是必须的，但是否用Redux就取决于你的需求了</li>
</ul>
<p>大概就是这样了。最佳实践不能帮你写代码，但可以少踩些坑。</p>
<p>话说，前端的混乱也是有历史原因的。因为以前的前端都很简单，我记得以前还用记事本写html和js，那时候写前端约等于“写脚本/写样式/做一些动态效果”。但现在前端越来越重了，越来越多的业务逻辑转移到前端，又赶上了移动互联网的大潮，复杂化/工程化是不可避免的。但却缺少约束和规范，野蛮生长，再加上一些先天的缺陷（比如原生js的各种坑爹设计），造成了如今的局面。</p>
<p>不过前端真的是很有活力的一个领域，有各种好玩&amp;吊炸天的东西。比如<a href="http://codepen.io/" target="_blank" rel="external">CodePen</a>、<a href="https://jsfiddle.net/" target="_blank" rel="external">JSFiddle</a>、<a href="http://threejs.org/" target="_blank" rel="external">three.js</a>、<a href="https://github.com/d3/d3/wiki/Gallery" target="_blank" rel="external">D3.js</a>、<a href="http://rap.taobao.org" target="_blank" rel="external">RAP</a>、<a href="http://periodic.famo.us/" target="_blank" rel="external">famo.us</a>、<a href="http://moebio.com/" target="_blank" rel="external">Moebio Labs</a>等等很多。</p>
<h1 id="React印象">React印象</h1>
<p>说实话，react给我的第一印象实在不咋地。因为我先看到的是JSX语法，感觉很难受，比如像下面这种：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">class</span> Hello extends React.Component {</div><div class="line">  render() {</div><div class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="title">div</span>&gt;</span><span class="tag">&lt;<span class="title">h1</span> <span class="attribute">className</span>=<span class="value">"testStyle"</span>&gt;</span>Hello, React!<span class="tag">&lt;/<span class="title">h1</span>&gt;</span><span class="tag">&lt;/<span class="title">div</span>&gt;</span>;</span></div><div class="line">  }</div><div class="line">}</div></pre></td></tr></table></figure>

<p>在js里写html，你特么在逗我吧？这是什么鬼？还记得以前的各种<code>xxx.innerHTML=</code>/<code>document.write(xxx)</code>的痛苦么。用js写html早就被证实是不可行了吧，极难维护而且扩展性堪忧，就跟JavaEE早期直接用servlet输出html代码一样。<br>我不喜欢angular一个原因就是它把太多逻辑揉到html里了，各种<code>ng-xx</code>标签。结果react反其道而行之，把html搞到业务逻辑代码里去了。。。</p>
<p>直到我看了<a href="http://www.hubwiz.com/class/552762019964049d1872fc88" target="_blank" rel="external">这个教程</a>，颇有茅塞顿开的感觉。这个教程从传统的DOM操作一步步过渡到react，很赞，很多教程上来就是一大堆react的概念，头都晕了。</p>
<p>原来React的<strong>本质还是DOM操作</strong>，它只不过额外做了几件事：</p>
<ul>
<li>提出虚拟DOM的概念，解决DOM操作效率低的痛点。至于它是如何做到DOM diff，如何将虚拟DOM渲染到真实DOM上的，完全不用关心。</li>
<li>在虚拟DOM的基础上，提出react元素/react组件的概念，强调高内聚，强调封装性，强调复用。React组件的本质就是一组DOM元素，这组DOM元素的样式和操作逻辑都被封装到组件中，并且可以被渲染到虚拟DOM上。围绕组件又会产生状态/props/生命周期等一系列概念。</li>
<li>提出JSX语法，解决DOM操作繁琐、不易读的痛点。JSX虽然看起来像html，但是一些细节的写法还是有区别的。只是一种语法糖，但习惯之后就再也离不开了。</li>
<li>在“一切皆组件”这个概念基础之上，提出各种“设计模式”，比如下文中的单向数据流/flux等，解决在大型项目中如何应用/解耦的问题。当然这些模式不是强制性的，算是react给出的最佳实践吧。</li>
</ul>
<p>可以说，react的本质就是对<strong>传统DOM操作的一个封装</strong>，使之更易用更易规模化。理解这点非常关键。如果对react的认识只停留在组件的层面，出现问题时就很容易懵，不知如何入手去解决。如果知道它背后其实是DOM，知道了react组件的渲染过程，很多概念都好理解了，包括对React的设计哲学也更容易理解。<br>python有“<a href="https://www.python.org/dev/peps/pep-0020/" target="_blank" rel="external">The Zen of Python</a>”，不知react有没有“The Zen of React”之类的啊。。。<br>此外，理解<a href="https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods" target="_blank" rel="external">生命周期</a>也非常重要，可以帮助理解react的设计理念。</p>
<p>话说，大公司真是有底气自己造轮子啊，JSX这种奇怪的语法，说搞就搞。个人自己造轮子的话，很容易死掉。</p>
<p>虚拟DOM算是react的一大创新吧，也是react能喊出“learn once, write everywhere”的关键。react给我们画了一个大饼：<br><img src="/2016/09/06/zhen-second-hand-qianduan/2.png" alt=""><br>大意就是虚拟DOM能被不同的平台渲染，就像JVM一样，我们只要针对虚拟DOM去写程序就可以了。看着很美好是吧，但<strong>太美好的东西注定不会实现的</strong>。首先跨平台的代码肯定要有些修改的，据说facebook自己用React Native也只能做到70%复用。其次DOM这种抽象结构真的能应付所有情况么？我看悬。</p>
<p>很多人喜欢对比react和angular，但其实二者并不是对等的。angular是典型的google风格，all in one，非常重量级，你前端开发需要的所有东西都能在angular中找到。它是一个封闭的圈子，有自己发明很多奇怪的概念，对你的编码方式/思路侵入性很强。react则如我之前所说，只是对DOM操作的一个封装，而且非常轻量级，抽象层次很浅，DOM做不到的东西，它也做不到。很多事情都要借助第三方。react上手很快，像我这种用惯了jquery的很容易就能理解它的思路。React Native也是大杀器啊，是不是以后我也可以说自己会开发app了😄。<br>而且，angular还有一个很大的劣势，就是1和2不兼容。。。而且2居然是用TypeScript写的。。。感觉这是邪路啊。微软在开源界的口碑可一向不怎么样。</p>
<h1 id="单向数据流">单向数据流</h1>
<p>单向数据流是react中很重要的一个哲学。我的理解，一个组件被渲染成什么样子，取决于：</p>
<ol>
<li>组件自身的状态。根据封装性原则，每个组件内部维护自己的状态，外界不能也不应该去干涉</li>
<li>外界传过来的props。就是组件对外界因素的响应，也是外界干涉组件的唯一方式</li>
</ol>
<p>一个是内因，一个是外因。<br>不知为何，写这段时我总是会想到草履虫。。。想到生物课上讲的“应激性”。。。</p>
<p>所谓单向数据流，就是指props只能从父组件流向子组件。这样，每个组件要做的事情就很简单了：1. 维护自己的状态；2. 接收外部的props并做出反应；3. 看情况是否更新子组件的props。<br>这样看来，每个组件的职责都很明确，非常利于解耦。</p>
<p>这样的设计很简单很美是不是？如果大家都能遵从这样的原则，世界就和平了。可惜，看着很美的设计往往都是有缺陷的，不能覆盖所有情况。就像不可变对象，能减少线程同步的问题，那就把所有对象都做成不可变的？are you kidding me ？<br>这个设计最大的问题是假设子组件与子组件之间不需要通信，并且父组件也无需获知子组件的状态。一些简单的应用确实可以这么假设。但只要稍微复杂一点的应用中，组件之间往往需要共享状态/互相传props，甚至子组件可能修改父组件的状态，为了达到这个目的，就只能写一些<a href="https://github.com/jiangxy/react-antd-admin/blob/master/src/components/DBTable/index.js" target="_blank" rel="external">很恶心的代码</a>：</p>
<ol>
<li>很多子组件自身的状态要“提升”到父组件中，只是为了能和其他子组件共享</li>
<li>props搞得很重，有很多不必要的属性在里面，因为是跟子组件交互的唯一方式</li>
<li>父组件必须传回调函数给子组件，这样子组件状态变化时才能通知到上层</li>
</ol>
<p>写这种代码很头疼，完全破坏了组件的封装性。而且状态的变化很难搞清楚，经常写着写着思路就乱了，要停下来想想。。。</p>
<p>为了解决这种问题，facebook提出了<a href="https://facebook.github.io/flux/docs/overview.html" target="_blank" rel="external">Flux</a>模式，可以类比常见的MVC模式。为啥facebook不套用MVC模式而要重新发明个轮子呢？好像跟react自身的特点有关，网上也有很多讨论。<br>注意这只是一种思路，可以有很多种具体实现，最著名的实现就是Redux，也算是React全家桶中的元老了。我没实际用过Redux，但我写过那种恶心的代码，知道它要解决的痛点是什么。。。我的理解，它提供了一个统一的状态存储的地方，而且数据变化时会有消息通知到对应的组件，有点类似消息队列？</p>
<p>当然Flux/Redux也不是万能的。引入这种模式必然会增加你的应用的复杂度，而且可能连带的引入一些其他的问题，是否使用还是要看自己的需求。如果你像我一样，写组件的过程中觉得组件之间通信太恶心了，别犹豫了快试试吧，人生苦短何必为难自己。。。</p>
<h1 id="状态是万恶之源">状态是万恶之源</h1>
<p>现在越来越觉得，在设计一个系统/模块的时候，“状态”才是优先要考虑的事，无论是前端/后端，无论什么需求，无论什么开发语言。</p>
<p>没有状态的东西（所谓“东西”，可能是一个对象/API/进程/模块/机器，甚至可能是更抽象的概念），就意味着可以快速复制出一个一模一样的。不用为状态操心，也可以避免很多潜在的坑，在解耦、可用性上会有很大的便利。一个例子是不可变对象；另一个印象深刻的例子是storm的nimbus进程，无状态所以随时重启；REST风格API也要求请求是无状态的；MapReduce的<code>share nothing</code>理念也是这样一个思路。与之相对的就是Oracle RAC，share everything，就会导致出问题时非常难处理。</p>
<p>状态让一切变得复杂。在函数式编程中，也会一再强调不可变/幂等性/无副作用的函数，一个道理。</p>
<p>但状态也不可能完全避免，否则这个系统还有啥用，就像人没了记忆一样。如何设计状态、如何设计状态之间的变化、如何设计系统对不同状态的响应，就是我们要思考的事情了。安利下一位大牛的文章：<a href="https://segmentfault.com/a/1190000005704433" target="_blank" rel="external">舌尖上的状态机</a>。</p>
<p>说回react。状态在react中是尤其重要的一件事，因为每个组件都可以有自己的状态，而状态的变化会自动触发render方法。所以，先整理好状态的变化是很重要的，不然写着写着代码就懵了。。。</p>
<p>一般的设计原则就是“最小化”，使用尽量少的状态，参考<a href="https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#what-components-should-have-state" target="_blank" rel="external">官方文档</a>。facebook甚至专门提出了“<a href="https://facebook.github.io/react/docs/reusable-components.html#stateless-functions" target="_blank" rel="external">无状态组件</a>”的概念。总之状态越少越好维护，但也不能矫枉过正。</p>
<h1 id="关于Ant_Design">关于Ant Design</h1>
<p>口水<a href="http://ant.design" target="_blank" rel="external">Ant Design</a>（简称antd）很久了，一直想研究下，终于得偿所愿。</p>
<p>对antd的第一印象就是“哎呦这个界面看着不错哦”。其实围绕React的UI KIT也有很多了，比较出名的是<a href="https://react-bootstrap.github.io/" target="_blank" rel="external">React-Bootstrap</a>和<a href="http://www.material-ui.com/#/" target="_blank" rel="external">Material-UI</a>。但antd最大的优势是语言，无论文档还是issues，都是中文，方便了很多。如果有几个差不多的开源项目摆在我面前，我当然选中文的那个啊。无论你英文多熟练，也还是中文看着舒服对不。而且antd社区也很活跃，版本更新很快，提出问题也能很快有人解答，用的人也算比较多。希望国内能有更多的这种开源项目啊。</p>
<p>最近antd又发布了<a href="http://mobile.ant.design/" target="_blank" rel="external">Ant Design Mobile</a>（简称antd-mobile），可以写移动端的H5了，对二手前端来说真是喜大普奔，我也写了个简单的<a href="https://github.com/jiangxy/react-antd-mobile-demo" target="_blank" rel="external">DEMO</a>。简单的需求都不用去求前端同学了，自己搞定。而且antd-mobile似乎也准备支持React Native，持续关注中。</p>
<p>antd对我而言，最大的意义就是不用写样式了（当然也不可能完全避免）。。。毕竟它已经封装好各种各样的组件，直接用就可以了。就像我之前说的，前端开发中最痛苦的事情就是写css。js好歹还算一门语言，对于标准的engineer来说都能很快上手，写各种业务逻辑驾轻就熟。但css就不一样了，每种样式在不同浏览器下有什么区别？常用的布局属性？各种overflow、display搞的头都要炸了。。。搞好css需要大量的经验和实践，不是我这种半路出家的二手前端能很快掌握的。<br>最关键的，我对自己的审美没什么信心。。。我觉得不错的界面，其他人觉得一般；我觉得还可以的界面，其他人一般觉得丑。。。</p>
<p>so，antd真是大救星啊。就像以前jquery时代的Bootstrap/Metronic/AdminLTE一样，但是好用了很多。而且也能追赶下React的潮流不是。</p>
<p>但我还是要吐槽下，<a href="http://ant.design/components/form/" target="_blank" rel="external">Form组件</a>实在是难用。。。这个组件跟其他组件的用法截然不同，各种东西都要从props里取，antd搞了很多黑盒的操作。但这也不能说是antd的锅，而是react的痼疾。react处理各种input元素，本来就很麻烦，见官方文档<a href="https://facebook.github.io/react/docs/forms.html#controlled-components" target="_blank" rel="external">Controlled Components</a>。这种设计会导致表单中输入项一多，很有可能会卡，因为每输入一个字符都要重新render。希望antd能想办法优化下吧。</p>
<h1 id="Webpack是个好东西">Webpack是个好东西</h1>
<p>有人说，前端开发一半的时间都在搭环境。其实很有道理，尤其在前端现在的混乱情况下，对于一个强迫症而言，想搭一个顺手的开发环境，真是各种纠结。</p>
<p>刚开始接触antd时，我直接使用了<a href="http://ant.design/docs/react/getting-started#标准开发" target="_blank" rel="external">官方的脚手架</a>。但我讨厌黑盒，很多东西如果我不了解基本的原理，用着心虚。官方的脚手架就是一个黑盒，在npm + webpack的基础上各种包装，搞出一些奇奇怪怪的东西，比如<a href="https://github.com/ant-design/antd-tools" target="_blank" rel="external">antd-tools</a>/<a href="https://github.com/ant-design/antd-init" target="_blank" rel="external">antd-init</a>/<a href="https://github.com/dora-js/dora" target="_blank" rel="external">dora</a>（这个好像不是antd团队搞出来的，但也是支付宝的）之类的，美其名曰一站式解决方案。要不怎么说前端喜欢自己造轮子呢。</p>
<p>也许对新人而言这种方式确实比较好吧，毕竟可以很快开始投入编码。但碰到问题怎么办？去研究这些黑盒？就算花很大力气研究明白了，对自己又有什么益处呢？与其研究这些自己造的轮子，我为啥不去直接去研究“业界标准”webpack呢？webpack如果研究明白了，以后很长时间都会受益吧。</p>
<p>同理我也不想用各种react工程模版，比如<a href="https://github.com/kriasoft/react-starter-kit" target="_blank" rel="external">这个</a>还是WebStorm官方推荐的，感觉都是加了特技的啊，各种不需要的功能都duang duang duang的扔进去。</p>
<p>于是我就走上了绕过官方脚手架，自己搭建环境的不归路。。。纠结了好久啊，结果见<a href="https://github.com/jiangxy/react-antd-admin/blob/master/docs/Structure.md" target="_blank" rel="external">这里</a>。</p>
<p>说回webpack。最开始我以为这是跟maven类似的一个构建工具。但实际用下来发现不太一样：</p>
<ul>
<li>webpack对工程结构没有任何约定</li>
<li>maven的依赖管理，用npm去实现，跟webpack没啥关系</li>
<li>webpack有点像maven-compiler-plugin插件的加强版</li>
</ul>
<p>说白了，webpack就是一个编译工具，又兼一点打包的功能。但它的强大之处在于：</p>
<ol>
<li>各种loader可以支持特定语法。比如babel-loader可以让你使用ES6甚至ES7的语法，也可以支持react特定的JSX语法。loader还可以实现各种神奇的功能，比如react-hot-loader，发挥自己的想象力吧。</li>
<li>各种plugin，也能实现很多神奇的功能。常见的就是压缩&amp;混淆代码。</li>
</ol>
<p>loader + plugin的机制让webpack变得非常灵活，可以各种自由定制，也让强迫症多了很多纠结的地方。。。最后我的配置文件见<a href="https://github.com/jiangxy/react-antd-admin/blob/master/webpack.config.js" target="_blank" rel="external">这里</a>。<br>React + Webpack的搭配，写起代码来非常爽。如果不用webpack，你的JSX语法就要单独编译，调试起来也很麻烦。而使用webpack的babel-loader，编译过程对你而言是完全透明的。再搭配上<a href="https://webpack.github.io/docs/webpack-dev-server.html" target="_blank" rel="external">webpack-dev-server</a>和<a href="https://github.com/gaearon/react-hot-loader" target="_blank" rel="external">react-hot-loader</a>，调试也非常方便。写React的基本都是这么一套配置。</p>
<p>但webpack似乎更适用于SPA，对于一些多页面的应用似乎不太好用。</p>
<h1 id="ES6也是个好东西">ES6也是个好东西</h1>
<p>很早就知道ES6，但一直没有大规模的用过，最多用用<code>for-of</code>之类的语法。总的感觉，ES6不算是一次彻底的变革，更像是在ES5的基础上加了各种语法糖，让写代码的时候能够更舒服点，但底层其实是没啥变化的。虽然各大浏览器还没有完全支持ES6，不过有了<a href="http://babeljs.io/" target="_blank" rel="external">Babel</a>这种神器，根本不用担心兼容性，最多可能加些运行时的polyfill。</p>
<p>我最常用的ES6特性，无非是class/import/箭头函数之类的，确实很爽。值得庆幸的是终于不用再跟prototype打交道了。我一直觉得js的prototype是个很奇怪的设计。。。强行面向对象。但如果要兼容一些老的代码，还是要用prototype。</p>
<p>感觉以前写js真的是“写脚本”，跟写bash之类的没啥区别；现在写js就真的是“写程序”了。其中区别，自己体验过才能理解。话说js越来越像java了。。。也越来越规范了。ES7都快出来了，这样强行每年一个版本真的好么。。。</p>
<p>ES6的教程我推荐<a href="http://www.infoq.com/cn/es6-in-depth/" target="_blank" rel="external">InfoQ的</a>，讲解很详细，还有<a href="http://www.infoq.com/cn/minibooks/ES6-in-Depth" target="_blank" rel="external">PDF版本</a>。当然事先要对js有些基础。</p>
<p>React能很好的兼容ES6，但写法上要有些变化。参考<a href="http://babeljs.io/blog/2015/06/07/react-on-es6-plus" target="_blank" rel="external">这篇文章</a>和<a href="https://facebook.github.io/react/docs/reusable-components.html#es6-classes" target="_blank" rel="external">官方文档</a>。至于说是用ES5风格的<code>React.createClass</code>还是ES6风格的<code>extends React.Component</code>，完全看个人喜好，反正经过babel之后应该都是没差别的。但个人倾向于ES6的写法，包括模块/集合等，也尽量遵循ES6的标准。</p>
<h1 id="零散的知识点">零散的知识点</h1>
<p>一些零散的东西，记录下备忘：</p>
<ol>
<li>this.props对当前组件而言是只读的</li>
<li>props和state变化都可能引起重新render</li>
<li>很多时候，组件是“单例”的</li>
<li>注意JSX中的<code>className</code>，这是React的一个妥协，不那么优雅</li>
<li>JSX中，内联样式必须通过js对象实现</li>
<li>JSX中，可以为React元素设置一个JSON对象作为属性包，使用<code>{...obj}</code>的语法</li>
<li>mixin感觉是继承的简化版，但很少用到。而且React在ES6语境下是不支持mixin的。</li>
<li>使用this.props.children就可以访问React子元素</li>
<li><code>React.findDOMNode(this.refs.q)</code>也是一个不那么优雅的设计，但很有用</li>
<li>现在跨域请求都流行用<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS" target="_blank" rel="external">CORS</a>了，不用JSONP了，不过貌似safari对CORS的支持有点问题</li>
</ol>
<h1 id="总结">总结</h1>
<p>至此，算是把自己对前端&amp;React的理解总结完毕了。</p>
<p>其实这篇文章2个月之前就想写了，那个时候就开始研究React了。但拖了很久，最主要的原因是我想把之前做的<a href="https://github.com/jiangxy/react-antd-admin" target="_blank" rel="external">通用后台</a>扔到github上，但从自己的一个玩具到一个公开的项目，还是有很多事情要做的。要完善各种边角的功能、测试各种流程、补充各种文档等等。再加上工作上事情确实比较多，所以进度很慢。好在趁着G20放假的期间，终于基本搞定了。<br>题外话，正确写一个user guide文档的方法：interesting hello world -&gt; overview -&gt; 分块讲解，写文档过程中的一点小小感悟，虽然这次也没用上就是了。。。</p>
<p>以前我也尝试过学习React，大多半途而废，看了下教程就放弃了。没想到这次居然能坚持下来，还搞了个简单的<a href="https://github.com/jiangxy/react-antd-admin" target="_blank" rel="external">项目</a>，我想了想原因：</p>
<ol>
<li>一个好的入门教程非常非常重要。很多人推荐<a href="http://www.ruanyifeng.com/blog/2015/03/react.html" target="_blank" rel="external">阮一峰的React教程</a>，但我看了还是感觉一头雾水。反而<a href="http://www.hubwiz.com/class/552762019964049d1872fc88" target="_blank" rel="external">这个教程</a>我感觉非常赞。教一个零基础的人和一个有基础的人，当然方法也不一样。所以适合自己的教程才是最好的。</li>
<li>Ant Design功不可没，要是没有antd，我估计写个Hello world就该干嘛干嘛去了。</li>
<li>根本原因：都是被逼的啊！没有前端来给我们写界面啊，只能自己上了啊。好在运营后台对前端性能一般没什么要求，易用性/用户体验上也可以商量，二手前端也能应付。。。</li>
</ol>
<p>话说，我身为一个java开发，第一次认真搞的开源项目居然React的，世界真是奇妙啊。。。<br>最后分享一个ppt吧，之前组内分享用的，<a href="https://pan.baidu.com/s/1geZkgsb" target="_blank" rel="external">《二手前端心得》</a>，不过技术人员做的ppt嘛，一般都不咋地。。。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p><a href="/2015/09/21/second-hand-frontend/">《二手的前端开发》</a>续集，继续折腾，跳了React的大坑。。。</p>
]]>
    
    </summary>
    
      <category term="react" scheme="http://jxy.me/tags/react/"/>
    
      <category term="前端" scheme="http://jxy.me/tags/%E5%89%8D%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[ML门外汉的哀嚎]]></title>
    <link href="http://jxy.me/2016/06/10/machine-learning-outsider/"/>
    <id>http://jxy.me/2016/06/10/machine-learning-outsider/</id>
    <published>2016-06-10T05:40:38.000Z</published>
    <updated>2016-06-29T10:05:28.000Z</updated>
    <content type="html"><![CDATA[<p>每月总有那么几天，不吐不快。</p>
<a id="more"></a>
<p>最近机缘巧合，研究了一些人工智能相关的东西。发现了各种槽点。。。<br>研究的头昏脑胀，总算稍微有点眉目。记录下。</p>
<p>我的理解不一定是对的，只是吐槽而已。<br>但我必须给自己一个能自圆其说的解释，否则寝食难安啊。</p>
<h1 id="啥是智能？">啥是智能？</h1>
<p>说到底，到底啥是智能？人工我能理解，但怎样才算是智能？通过图灵测试就是智能了？会解方程是不是也是一种智能？会矩阵分解就是更高层次的智能了？AI(Artificial Intelligence)这个词的边界，到底在哪里？也许根本就没有边界吧。。。这更像是个营销词汇，让围观群众们不明觉历罢了。就像所谓的bigdata，也是个没有边界的词，适合营销，但不适合作为严格的定义。<br>所以我很能理解为什么70年代AI那么火，这名字听着就NB啊。不过火了一段时间就沉寂下来了，因为大家发现似乎也没有那么NB。。。机器还是只能做一些简单的事。莫非bigdata也要重复这条老路。。。</p>
<p>如果认为只要能解决问题就算AI，那我们是不是发明AI好多年了。。。各种软件都算是一定程度上的AI了。不过大家感情上可能很难接受。就像我小时候觉得，机器人一定是有手有脚，长得像人一样的。结果某天电视上指着一个机械臂跟我说“机器人生产线替代人类”之类的，我幼小的心灵受到了冲击——只有手臂连手指都没有，也特么能算机器人？</p>
<p>当然也可能AI有自已的严格定义，但是我不知道。不过话说回来，AI说是Computer Science的终极目标也不为过吧。就像统一场论，就像理想国，就像那啥主义一样，都是我们的美好愿景。毕竟不能只有眼前的代码，也要有诗和远方嘛。</p>
<p>而且人工智能的研究还有个特点，有点像个黑盒，学术点说就是“可解释性”不强。比如最近很火的深度学习，可能稍微改改参数效果就会提升很多，但是原因大家都说不清楚。。。光是调参数就能搞出很多paper来。“我不知道我是怎么做到的，反正就是做到了”。。。</p>
<p>不过也许AI确实应该是个黑盒。对于人类而言，我们碰到问题，会经过自己的“思考”、各种“推理”，再形成自己的答案。但我们自己也说不清中间的过程是怎样的。如果机器直接知道所有问题的答案，类似一个Map，每个问题它都能直接给出答案，那它算是有“智能”么？虽然它可以对所有问题给出正确答案，但却没有经过“思考”的过程。如果把它看作一个黑盒，确实应该算是有智能了。有点像大刘的“诗云”的赶脚。。。如果机器能穷举出所有可能的诗，我们就能说它会做诗么？<br>不过图灵测试是不管这些的，它不限制你的方法，无论是基于规则的还是基于统计的，反正表现的像个人就行。有点像python的duck-typing机制，“叫起来像鸭子，游起来像鸭子，那它就是鸭子”。</p>
<h1 id="名词控">名词控</h1>
<p>对于人工智能我是没什么基础的，只知道一些简单的机器学习算法，分类、聚类啥的。但是总是会听说很多高大上的名词：人工智能/机器学习/深度学习/神经网络。。。看着就晕，他们之间是什么关系？</p>
<p>也许大概是下面这样：<br><img src="/2016/06/10/machine-learning-outsider/1.png" alt=""></p>
<p>卧槽这图怎么这么大，还不能缩小。</p>
<p>人工智能，如上所述，是非常非常宽泛的一个概念。为了实现人工智能，人们尝试了很多种方法，所以这里面有很多派别。比较出名的就是基于规则的专家系统，把人类世界的各种“知识”作为规则灌输给机器人，这也是比较容易想到的一条路。所以很多新闻为了吸引眼球就会鼓吹“机器人已经学会了整套大英百科全书”。但是事实证明这条路是走不通的，因为“规则”是无穷尽的。这派很出名的人物就是<a href="https://en.wikipedia.org/wiki/Marvin_Minsky" target="_blank" rel="external">明斯克</a>，高中时我就从一些科普读物中知道这个人，不过是作为学术霸权的反面典型出现的。。。</p>
<p>机器学习，是人工智能比较成功的一个分支，很多时候可以甚至可以说是代表了人工智能。如果说专家系统是基于理想主义的，让机器去“理解”这个世界，那机器学习就是基于实用主义的，我不管机器是不是真的“理解”，反正它给我的结果能用就行。机器学习<strong>本质上是基于统计</strong>的，去发现数据的内部规律，进而预测一些未来的情况。但机器学习能做的其实也很有限，大多数情况下只是用一个函数去拟合训练数据，也就是所谓的“模型(model)”，或者是“假设(hypothesis)”，然后拿着这个函数去预测未来的值，当然实际上肯定没我说的这么简单。但有些问题是不能用这个方法解决的啊，比如“如何找到女朋友”这种问题。。。</p>
<p>神经网络，简称ANN，是机器学习理论中提出的一个模型。也有叫做“感知机(perceptron)”、“多层感知机(MLP)”的，都一个意思，历史遗留问题。是对人类神经系统的一种简单抽象，看着好像很高大上的样子，说到底其实就是用来模拟一些函数的工具。之前说过，机器学习通常都要用一个函数去拟合训练数据，怎样找到这个函数？如果我们假设这个函数是线性的，比如一条直线，那就比较好找到。如果是更高阶、更复杂的函数咋办？数学上可以证明，有一个隐层的神经网络就可以逼近任意复杂的函数。别问我咋证明的，我一介学渣怎么会知道。。。题外话，复杂的函数真的可以很复杂，经常在zhihu上看到一些诡异的函数图形。。。<br><img src="/2016/06/10/machine-learning-outsider/2.png" alt=""><br>如上图，整个系统分多个层次，一个输入层，一个输出层，中间还有多个隐层。按传统的定义，这是一个2层的神经网络，不算输入层。每个神经元接受上一层的多个输入值，经过自己的一些运算，再给出输出值。有几点要注意的：</p>
<ol>
<li>按传统的定义，每个节点的运算都只是简单的sum，然后经过一个“激活函数”，决定输出是0还是1，这个激活函数有很多种选择，比较常用的是sigmod函数。但我不是很清楚为何要这样设计，如果每个节点的计算过程更复杂会怎样？如果输出的不止是0和1又会怎样？也许数学上可以证明没必要的吧，我不清楚。每一层都可以指定激活函数，线性/非线性都可以，不知道同一层中是否可以选定不同的激活函数。</li>
<li>每个节点处理输入的时候是有权重的，或者说，上图中的“连接”是有权重的。确定权重的过程就是学习的过程。</li>
<li>每个节点不一定要接受上一层所有的输出，换句话说，上图不一定是“全连接”的。如何连接各个节点也是门学问。</li>
</ol>
<p>没有隐层的神经网络，最初被称为“感知机”。明斯克为啥被称作学术霸权，就是因为他写了本书把感知机批判的一无是处，最著名的一个黑点就是感知机不能解决异或(XOR)问题，因为这是无法用简单的线性函数来拟合的。他还断言多层的神经网络也没啥前途，因为计算量太大，无法训练。他的权威导致ANN相关的研究停滞了很久，直到BP算法被提出来，ANN才又火起来。</p>
<p>ANN最常用的地方是分类。很多机器学习中的常见算法，比如逻辑回归、SVM之类的，本质上都可以看作ANN的特例。<br>这个时候的ANN一般都只有一个隐层，因为理论上一个隐层的ANN就能模拟任意函数，而且层数太多的话不好训练，计算量大。BP算法是从后向前训练的，如果层数太多残差传到前面的层就会很小，出现所谓的梯度扩散，训练出来的模型就会不准。</p>
<p>另外我还有点不太明白，既然只是用来模拟复杂函数的工具，为啥会套上神经网络这么一个高大上的名词。也许最开始的感知机确实是在模拟神经元的运作机制，但发展到ANN后，已经跟神经系统没啥关系了。有句话说的好：“不再盲目模拟人脑网络是神经网络研究走向成熟的标志”。</p>
<p>ANN最为人诟病的一点就是“不可解释”，没有一个可靠的理论依据。隐层要用多少个节点？各个节点之间如何连接？为何这样连接的效果更好？很多时候只能凭经验。而且训练起来也确实很复杂，所以在SVM等算法提出后，ANN的研究又一次沉寂下去。</p>
<p>深度学习，ANN借尸还魂，新瓶装旧酒。2006年，又是<a href="https://en.wikipedia.org/wiki/Geoffrey_Hinton" target="_blank" rel="external">Geoffrey Hinton</a>（BP算法也是他提出的）在Science上发表了一篇论文，证明了多层神经网络的优越性，可以大幅提高准确率，而且训练上的复杂性可以用一些工程上/数学上的技巧克服。他给这种技术起了一个新的名字：深度学习，由此掀起了深度学习的热潮，至今未衰。我觉得深度学习能火爆的几个原因：</p>
<ol>
<li>从理论上证明了多层神经网络有更高的上限，换句话说可以更好的拟合，但也有可能过拟合。</li>
<li>可以用一些技巧克服训练上的难度。</li>
<li>大数据的发展，包括GPU运算的发展，数据量和计算能力都有了极大提升。原来一些不可能完成的计算量现在都是小儿科。</li>
</ol>
<p>通过研究发现，在参数数量一样的情况下，更深的网络往往具有比浅层的网络更好的识别效率。别问我为啥。。。反正DL很好很强大就是了。。。这种程度的研究，一般的学霸都搞不定，更何况我这种学渣了。<br>深度学习作为ANN的继任者，做出了很多创新，但是也继承了一些缺点，比如之前说的“不可解释”。</p>
<p>整个AI领域的发展过程大概就是这样了。AI是一门很古老的学科，很多工作从上世纪四五十年代就开始了，尤其是ANN，经历了很多起起落落。但真的不要对AI报太大希望，机器能做的事真的是非常有限的，AI发展了这么久，能做的也就是预测/分类/聚类。当然很多现实问题都可以抽象为这几种。“怎么找女朋友”的问题，估计它永远都解决不了。。。<br>感觉AI的发展过程和NLP有点像啊，最初大家都是试图基于规则去做，让机器去“理解”。但都走不通，最后回归到基于统计的方法上。</p>
<p>既然说到NLP，就再罗嗦几句。我一直奇怪NLP算不算是AI领域的？也许不算吧，感觉NLP是一个交叉学科，会用到机器学习的一些东西，但不全是机器学习，会用到很多其他技术。也许分词/文本分类算是机器学习，但词性标注之类的应该不算。<br>推荐系统也是一个交叉学科，会用到机器学习一些技术，但又不完全是。<br>数据挖掘同理。<br>这些学科，更像是各种技术的集合，就像计算机科学是电气/数学/物理等的集合一样。</p>
<h1 id="机器学习">机器学习</h1>
<p>扯了半天终于扯到正题了。。。下面只是我自己的理解，可能有些是错误的。</p>
<p>机器学习算法跟普通的算法设计不太一样。普通的算法，比如排序/DFS之类的，算法逻辑是“固定”的，可以应用到任意输入上，算法的行为不会因为输入的不同而改变。但机器学习的算法非常依赖数据，就像之前说的，它是用一个函数去刻画已有数据的内部规律（实际上不一定是函数），然后去预测未来的数据。如果输入的数据量不够大，或者质量不好，它生成的函数（或者说模型）效果就会很差。说到底，它还是脱胎于统计学的，如果样本数量不够，统计学是无能为力的。</p>
<p>其实“机器学习”这个词也很难定义，边界也很模糊。。。与其研究这些形而上的东西，不如直接看看机器学习中有哪些算法、能帮我们做到什么。</p>
<p>几个基本概念：</p>
<ul>
<li>特征(feature)：这是最关键的东西，通俗点说，就是从哪些维度去描述一个事物。比如“早上好”这句话，如果以词为特征，可能是[“早上”, “好”]；如果以字为特征，可能是[“早”,”上”,”好”]；如果以字数为特征，就是[“3”]。而且这些特征可以任意组合。特征选的好不好会直接影响算法效果。而且特征的选取是很难的，很多时候只能凭经验。特征一般会用一个向量表示。</li>
<li>目标(target)：就是要预测的值。对于有些算法而言其实没有target，比如聚类。</li>
<li>数据集：用于统计分析的样本数据。数据一般会分成训练集和测试集。所谓的数据，很多时候就是一个features-&gt;target的映射。</li>
<li>假设(hypothesis)：就是训练所得的函数，由于一些历史原因，被称作hypothesis。</li>
</ul>
<p>按训练数据集的类型分类：</p>
<ul>
<li>监督学习。输入的数据是有标注的，或者说，是有target的。监督学习最常解决的问题就是回归和分类。回归一般用于预测一个连续的值，分类用于预测一个离散的值。分类算法又可以分为二分类、多分类等等。其实现实世界中的很多问题都可以抽象为分类问题，所以分类算法应用很广。</li>
<li>无监督学习。输入的数据没有target，只有features。比如聚类。我只稍微知道一点k-means。</li>
</ul>
<p>但无论如何，数据都是必须要有features的，无论这个features是人工选取的，还是计算出来的。如果要为一个人选取特征，我们可以手工选取性别/年龄/身高/肤色之类的，这是因为我们有着“经验”，或者叫做“先验知识”/<a href="https://en.wikipedia.org/wiki/Inductive_bias" target="_blank" rel="external">Inductive_bias</a>，我们知道这些属性“足够”描述一个人。但对于未知的数据，我们该如何选取特征？比如我们预测一个人该去男厕所还是女厕所，明显可以根据性别分类，但训练数据中的特征却不包含性别，只有身高/肤色之类的，于是呵呵了。。。训练出来的模型可能跟实际偏差非常大。有一种方法是“乱枪打鸟”，把所有可能的特征都选出来，总会有一些特征能生效的。似乎深度学习中就是应用了这种思想。但过多的特征也会造成计算量增加，要自己权衡的。</p>
<p>前面说过，机器学习大多是用一个函数刻画数据的内部规律（或者说去拟合数据），得出这个函数的过程就是训练过程。但这个描述也不是很准确：</p>
<ol>
<li>数据的内部规律，未必能用一个函数来描述，还有各种树模型、XX模型之类的。其实数据到底有没有规律也不过好说。。。数据的内部规律是我们假设出来的，我们假设它是线性的，于是才会用一个线性函数去拟合。也可能数据根本是没有规律的，但我们也能强行搞出一个线性函数，只是效果很差罢了。</li>
<li>不是所有的机器学习算法都有训练过程。无监督的算法肯定都没有训练过程的，一些有监督的算法也没有训练过程，比如KNN。是否有训练过程，也是取决于我们对数据规律做了怎样的“假设”。</li>
</ol>
<p>关于Inductive_bias，<a href="https://www.zhihu.com/question/29271217/answer/45665100" target="_blank" rel="external">这个zhihu帖子</a>讲的很好。</p>
<p>一般过程：</p>
<ol>
<li>准备数据。特征选取、归一化。这是所有算法都要做的。特征的选取之前有说过，归一化是为了让特征更“规整”，不会让某些特征影响过大。</li>
<li>训练模型。如前所述，不是所有算法都有训练过程的。训练过程目的是为了让模型的误差最小，或者说是找到loss function（也有叫做cost function/error function的）的最小值，常用梯度下降法。这里有很复杂的理论，如何处理局部最优，如何处理过拟合等等。</li>
<li>使用模型。使用训练好的模型预测新的数据。</li>
</ol>
<p>机器学习的理论也很古老了。。。但受限于计算能力，似乎一直没有什么很成功的应用。也可能是我孤陋寡闻。好像之前有什么无人驾驶汽车就是用机器学习算法驱动的。近些年随着bigdata的火热，计算能力/数据处理能力大幅增加，各种开源的算法库的出现（mahout、spark mllib），感觉机器学习的门槛下降很多，相关的应用也多起来了。</p>
<h1 id="深度学习">深度学习</h1>
<p>这块我就更是不懂了。。。只是看了些资料。<br>上面已经说过，深度学习是在ANN基础上发展而来，摘录一段话：</p>
<blockquote>深度学习的实质，是通过构建具有很多隐层的机器学习模型和海量的训练数据，来学习更有用的特征，从而最终提升分类或预测的准确性。因此，“深度模型”是手段，“特征学习”是目的。区别于传统的浅层学习，深度学习的不同在于：1）强调了模型结构的深度，通常有5层、6层，甚至10多层的隐层节点；2）明确突出了特征学习的重要性，也就是说，通过逐层特征变换，将样本在原空间的特征表示变换到一个新特征空间，从而使分类或预测更加容易。与人工规则构造特征的方法相比，利用大数据来学习特征，更能够刻画数据的丰富内在信息。</blockquote>

<p>是不是特抽象？每一个字我都认识，但这段话到底是在说啥。。。</p>
<p>之前说过，选择特征是非常麻烦的事情。特征选的不好，算法效果就会很差。而人工选择特征又很麻烦，只能凭经验。所以能不能让机器自己选择特征？<br>深度学习可以部分达到这个目标。它可以将原始的特征，经过若干次变化，生成更多的特征。比如输入的特征是[x, y]，它可以将这个特征变成[x, y, sin(x), y^2, x*y]之类的，当然实际上没这么简单。这就是上文所说的“将样本在原空间的特征表示变换到一个新特征空间”，新生成的特征能更好的描述数据的本质，分类/回归的效果也就会更好。别问我为啥，我也不知道。。。<br>那原始特征如何产生呢？也许可以用一些简单的方法吧，比如字向量之类的，不清楚。<br>而且这种特征的变换不是一次性的，跟网络的深度有关。<br>深度学习又被叫做“无监督特征学习（Unsupervised Feature Learning）”，就是因为它实际上是一个学习特征的过程。</p>
<p>深度学习的训练过程，忘了摘录自哪里了：</p>
<ol>
<li>使用自下上升非监督学习，这个过程可以看作是feature learning过程（或者说，它只是学习获得了一个可以良好代表输入的特征，这个特征可以最大程度上代表原输入信号）</li>
<li>自顶向下的监督学习（就是通过带标签的数据去训练，误差自顶向下传输，对网络进行微调）</li>
</ol>
<p>深度学习的网络是逐层训练的(layer-wised training)，之前有个段子就是“专业卷积神经网络训练，每层5元”。。。在所有层都训练完之后，再进行一些微调(fine-tuning)。主要是通过这两种方式，克服传统ANN中训练的难度。</p>
<p>深度学习的训练过程可以是无监督/有监督的，而传统的ANN都是有监督的。但分类系统不可能是无监督的，即使是深度学习，输出的最后也要接一个分类器。</p>
<blockquote>虽说非监督（如DBM方法）是深度学习的一个优势，深度学习当然也可用于带监督的情况（也即给予了用户手动标注的机会），实际上带监督的CNN方法目前就应用得越来越多，乃至正在超越DBM。</blockquote>

<p>另外卷积神经网络（CNN）好像和深度学习其实没啥关系，93年就出现了，本质上还是传统的ANN。</p>
<p>深度学习还有个特点，训练时似乎没有分布式的版本，大家都是单机上搞一堆GPU去训练。似乎有人说分布式训练的效果还不如单机？已有的一些工具，比如<a href="https://www.tensorflow.org/" target="_blank" rel="external">TensorFlow</a>/<a href="http://caffe.berkeleyvision.org/" target="_blank" rel="external">Caffe</a>/<a href="http://torch.ch/" target="_blank" rel="external">Torch</a>，也都是单机。spark作为大数据领域集大成者，没有包含深度学习相关的工具，也是有点奇怪。。。<a href="http://geek.csdn.net/news/detail/82614" target="_blank" rel="external">Spark Summit 2016</a>中提到了一些，Caffe on Spark之类的，好像挺有意思。</p>
<h1 id="聊天机器人">聊天机器人</h1>
<p>聊天机器人的概念也有些年头了。。。但是这个概念似乎最近又火了起来，可能是各大公司带了波节奏吧，见<a href="http://36kr.com/p/5048193.html" target="_blank" rel="external">这里</a>/<a href="http://tech.qq.com/a/20160623/064924.htm" target="_blank" rel="external">还有这里</a>，连<a href="http://36kr.com/p/5047975.html" target="_blank" rel="external">盈利模式</a>都想好了。甚至有人说，Facebook Messenger可以变成App Store，大家都在上面开发bot，重现当年开发app的狂热。</p>
<p>其实语音助手也出现好多年了吧，比如siri。当年某个小伙伴还很自豪的跟我炫耀：“phone my little brother”——于是自动给某人打电话。为啥现在突然火了？是因为app太重了？于是想用bot去轻量级的承载各种服务？其实有点类似微信公众号，很多尝试性的业务都适合用微信公众号来做，直接做app成本太高，而且app获取用户太难了。<br>微信公众号也算是个简易版机器人了，就是看起来没那么智能。<br>大家都把前景描述的很美好，自动分析你的需求/个性化推荐/帮你叫外卖之类的。我倒是谨慎乐观，想想人工智能的前几波浪潮吧。</p>
<p>YY了机器人的几种做法：</p>
<ol>
<li>模式匹配。很简单，就是个问答系统，像是数据库的like语句。用户输入某个句子，系统尝试在已有的语料里寻找，命中则返回。这是最容易想到的方式。</li>
<li>机器学习。用一大堆已有的语料去训练，然后让机器人自己预测下一句。但是缺陷很大，语料都是死的，机器人只能严格匹配，换一种说法它就无法处理了。而且它也不能触发服务，比如语料中显示“明天下雨么”的下一句是“会下雨”，这只是个special case。正确的行为是根据天气预报给出答案，机器人却只会机械回复“会下雨”。</li>
<li>语义理解。让机器人去理解一句话的意思，再根据用户意图选择回答，这里还是要靠规则。我对NLP完全不了解，不知道能不能做到这种程度。有个很有趣的服务<a href="http://wit.ai" target="_blank" rel="external">wit.ai</a>似乎可以自动帮你分析语义，不知道实际效果如何。</li>
</ol>
<p>感觉上，模式匹配还是主流。。。虽然low，需要很多人工的工作，但对用户体验比较好。其他的实现方式都可能出现前言不搭后语、逻辑混乱的情况。。。最著名的开源机器人应该是<a href="http://www.alicebot.org" target="_blank" rel="external">A.L.I.C.E.</a>吧，大概看了下它的实现，本质上也是模式匹配。它最大的贡献是AIML标准。不过这个项目也很古老了。最近试用了老东家的客服机器人<a href="http://qiyukf.com" target="_blank" rel="external">网易七鱼</a>，也是模式匹配，需要自己录入各种关键词和对应的回答，不知宣传中的语义分析和深度学习用在哪里。。。</p>
<p>对机器人而言，录入语料是最大的麻烦事。如何结构化？结构化到什么程度？如何尽量减少人工的工作？如何平衡机器的“智能”和人工的规则？<br>我理想中的机器人，直接把一本书灌给它，它自己提取出其中的对话并学习，然后把这些对话应到到聊天中。或者直接灌一些聊天记录给它。当然这个有点太理想化了。。。</p>
<h1 id="一些资料">一些资料</h1>
<p><a href="http://www.andrewng.org/" target="_blank" rel="external">Andrew Ng</a>的机器学习公开课，经典中的经典，这门课在Coursera上还有<a href="https://zh.coursera.org/learn/machine-learning" target="_blank" rel="external">另外一个版本</a>：<br><a href="http://open.163.com/special/opencourse/machinelearning.html" target="_blank" rel="external">http://open.163.com/special/opencourse/machinelearning.html</a></p>
<p>给出了一些matlab中的使用实例，对于理解很有帮助：<br><a href="http://www.cnblogs.com/heaad/archive/2011/03/07/1976443.html" target="_blank" rel="external">http://www.cnblogs.com/heaad/archive/2011/03/07/1976443.html</a></p>
<p>讲了各种历史，非常赞：<br><a href="http://www.36dsj.com/archives/39775" target="_blank" rel="external">http://www.36dsj.com/archives/39775</a></p>
<p>非常详细的资料集合：<br><a href="http://blog.sina.com.cn/s/blog_6a1b8c6b0101h9ho.html" target="_blank" rel="external">http://blog.sina.com.cn/s/blog_6a1b8c6b0101h9ho.html</a></p>
<p>关于ANN的衰落讲的较好：<br><a href="http://36kr.com/p/208510.html" target="_blank" rel="external">http://36kr.com/p/208510.html</a></p>
<p>PCA降维：<br><a href="http://blog.csdn.net/watkinsong/article/details/8234766" target="_blank" rel="external">http://blog.csdn.net/watkinsong/article/details/8234766</a></p>
<p>Google随tensor flow开源而开放的一个小玩具，可以帮助理解一些概念，很有意思：<br><a href="http://playground.tensorflow.org/" target="_blank" rel="external">http://playground.tensorflow.org/</a></p>
<p><a href="http://news.cnblogs.com/n/547170/" target="_blank" rel="external">各位大佬，别再拿人工智能当春药了！</a><br>确实现在人工智能有过度炒作之嫌，就像当初的云计算，现在的大数据一样。</p>
<p>其他一些<br><a href="http://hahack.com/reading/ann2/" target="_blank" rel="external">http://hahack.com/reading/ann2/</a><br><a href="http://deeplearning.stanford.edu/wiki/index.php/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C" target="_blank" rel="external">http://deeplearning.stanford.edu/wiki/index.php/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C</a><br><a href="http://nautidea.com/nautidea/articles/5694f6449eb5f06447e7a87b" target="_blank" rel="external">http://nautidea.com/nautidea/articles/5694f6449eb5f06447e7a87b</a><br><a href="http://blog.sciencenet.cn/blog-4099-785174.html" target="_blank" rel="external">http://blog.sciencenet.cn/blog-4099-785174.html</a></p>
<p>几篇访谈，了解大牛们的想法，很赞：<br><a href="http://www.infoq.com/cn/articles/interview-yann-lecun" target="_blank" rel="external">对话Facebook人工智能实验室主任、深度学习专家Yann LeCun</a></p>
<p><blockquote>人工智能的每一个新浪潮，都会带来这么一段从盲目乐观到不理智最后到沮丧的阶段。感知机技术、基于规则的专家系统、神经网络、图模型、支持向量机甚至是深度学习，无一例外，直到我们找到新的技术。当然这些技术，从来就不是完全失败的，它们为我们带来了新的工具、概念和算法</blockquote><br><a href="http://chuansong.me/n/362966942881" target="_blank" rel="external">原百度深度学习研究院创始人余凯谈人工智能大趋势</a></p>
<p><blockquote>其他的机器学习的方法可能随着数据的增加，性能提高到某一个点就饱和了。但目前为止对于深度学习我们还没有观察到这点，这可能是它最值得关注的地方</blockquote><br><a href="http://36kr.com/p/212692.html" target="_blank" rel="external">百度最强大脑在想什么？ —— 36氪硅谷专访百度首席科学家 Andrew Ng</a><br>Ng去了百度让人很意外。。。</p>
<h1 id="无关的读后感">无关的读后感</h1>
<p>其实跟本文没啥关系，只是最近读的一些文章而已，单独开篇也不值得，塞到这里好了。</p>
<p><a href="http://coolshell.cn/articles/17295.html" target="_blank" rel="external">让我们来谈谈分工</a>：有句话讲的很好：“作为工作的人，当你选择工作或任务的时候，你是选择做支持性的工作，还是做产出性的工作？你是选择做劳动密集型重复工作，还是做知识密集型的创新性的工作？”每个人都会有自己的答案，但很多时候这不是自己能决定的。创新性的工作失败风险会更大，如果失败，是否有退路？创新性的工作可能很久都没有产出，评KPI会很吃亏，自己是否能承受？</p>
<p><a href="http://www.freebuf.com/articles/web/29942.html" target="_blank" rel="external">SQL注入之SQLmap入门</a>：听了一个安全方面的分享才知道sqlmap，现在工具都这么智能了啊。。。我记得以前都要手动加单引号什么的，再一遍遍构造各种奇怪的url。</p>
<p><a href="http://www.infoq.com/cn/articles/exploration-of-distributed-mysql-cluster-scheme" target="_blank" rel="external">分布式MySQL集群方案的探索与思考</a>：数据库中间件是每个公司都会碰到的问题，本文详述了各种实现方案，很赞。</p>
<p><a href="http://www.infoq.com/cn/articles/https-difficult" target="_blank" rel="external">HTTPS之难</a>：运营商劫持太严重了，所以我们的一些系统也在做https改造。好在有统一的网关可以做掉。我觉得最难的地方是很可能考虑不周全。。。以为只要改掉系统A的接口就可以，结果又牵连到系统B，系统B又会牵涉到C。https改造是真正的牵一发而动全身。</p>
<h1 id="碎碎念">碎碎念</h1>
<ol>
<li>怎么又写了这么多。大概是拖的太久了。。。</li>
<li>最近领悟了一个原则：基础比什么都重要。招人的时候，即使对方没有任何业务经验，只要基础好，都不是问题。基础好的人可以快速切到其他领域，无论是新的技术还是新的业务。我就只能算是一个标准的engineer，说不上好也说不上坏。。。</li>
<li>所谓的“全栈”，不是说你什么都要会。而是需要你会的时候，你能很快的学会。所以基础很重要。</li>
<li>不要总想着高大上的做法，duck-typing，只要能达到效果就好。但也不能搞一堆if-else。。。有人跟我说写业务代码就是一堆if-else。。。还是要适度。</li>
<li>线性代数非常神奇，可以简化很多写法/计算，虽然我忘得差不多了。我特么就记得矩阵乘法了。</li>
<li>能把一门技术以通俗易懂的形式讲出来，是非常了不起的。我见过很多人/很多presentation，搞各种高大上的名词把人弄晕。要么是那个人确实有货，但表达不好，要么就根本是徒有其表。</li>
<li>工作烦躁的时候就去刷刷leetcode，冷静下，效果挺好的。不过刷题很快，写题解很慢啊。。。不知猴年马月才能写完。</li>
<li>ML真的不是make love的缩写么？无所谓了反正都是门外汉。。。</li>
</ol>
]]></content>
    <summary type="html">
    <![CDATA[<p>每月总有那么几天，不吐不快。</p>
]]>
    
    </summary>
    
      <category term="杂谈" scheme="http://jxy.me/tags/%E6%9D%82%E8%B0%88/"/>
    
      <category term="machine learning" scheme="http://jxy.me/tags/machine-learning/"/>
    
  </entry>
  
</feed>
