<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Yang&apos;s Blog</title>
    <description>每个人的一生都是一次远行</description>
    <link>https://yang.observer/</link>
    <atom:link href="https://yang.observer/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Mon, 16 Dec 2024 15:04:35 +0000</pubDate>
    <lastBuildDate>Mon, 16 Dec 2024 15:04:35 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>分区</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#如何分区&quot; id=&quot;markdown-toc-如何分区&quot;&gt;如何分区&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#按照关键字范围分区&quot; id=&quot;markdown-toc-按照关键字范围分区&quot;&gt;按照关键字范围分区&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#按照哈希分区&quot; id=&quot;markdown-toc-按照哈希分区&quot;&gt;按照哈希分区&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#二级索引&quot; id=&quot;markdown-toc-二级索引&quot;&gt;二级索引&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#二级索引按照主键分区&quot; id=&quot;markdown-toc-二级索引按照主键分区&quot;&gt;二级索引按照主键分区&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#二级索引按照其他键分区&quot; id=&quot;markdown-toc-二级索引按照其他键分区&quot;&gt;二级索引按照其他键分区&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#分区再平衡&quot; id=&quot;markdown-toc-分区再平衡&quot;&gt;分区再平衡&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#固定数量分区&quot; id=&quot;markdown-toc-固定数量分区&quot;&gt;固定数量分区&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#动态分区&quot; id=&quot;markdown-toc-动态分区&quot;&gt;动态分区&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#自动还是手动&quot; id=&quot;markdown-toc-自动还是手动&quot;&gt;自动还是手动&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#路由&quot; id=&quot;markdown-toc-路由&quot;&gt;路由&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考&quot; id=&quot;markdown-toc-参考&quot;&gt;参考&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最近在看&lt;a href=&quot;https://book.douban.com/subject/26197294/&quot;&gt;《Designing Data-Intensive Applications》&lt;/a&gt;，本文主要记录书中第五章《分区》的内容，加深自己的理解。&lt;/p&gt;

&lt;p&gt;分区(Partitioning)，也叫水平分割，是一种通过把不同用户分散到不同服务器上从而提升系统整体负载的方法。上一篇文章介绍过&lt;a href=&quot;https://yang.observer/2022/02/26/ddia-replication/&quot;&gt;《复制》&lt;/a&gt;，有时分区和复制会混合使用，如下图所示，有4个分区，每个分区的数据有3个副本，而且3个副本在不同的物理机器上，这样可以防止一个副本所在的物理机器故障导致整个分区不可用的情况。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/partitioning-replication.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;如何分区&quot;&gt;如何分区&lt;/h1&gt;

&lt;h2 id=&quot;按照关键字范围分区&quot;&gt;按照关键字范围分区&lt;/h2&gt;

&lt;p&gt;第一种分区方法是按照关键字范围分区。比如按照用户ID划分，[0, 1000)属于第一个分区，[1000, 2000)属于第二个分区，以此类推。下图的百科全书按照字典顺序把条目分到了12本书中。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/partitioning1.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种方式的优点是范围查询比较方便，因为相邻的ID在同一个分区。&lt;/p&gt;

&lt;p&gt;缺点是可能出现分布不均匀的情况，造成性能热点。比如以时间作为key，写入使用当前时间，往往会落入同一个分区。查询往往查询最近几天的数据，也会落入同一个分区。再比如按照用户ID划分的游戏服务器，开服时用户落在第一个分区，满了后再开第二个分区，这时前面几个分区是性能热点，过了一段时间，老的用户可能流失了，前面几个分区可能没什么访问量了，此时又要合并分区。&lt;/p&gt;

&lt;h2 id=&quot;按照哈希分区&quot;&gt;按照哈希分区&lt;/h2&gt;

&lt;p&gt;第二种方式是按照哈希分区。一种想当然的方案是 hash(id) % server_count，这种方案有个问题是如果服务器数量变了，每个id很可能会落在不同的服务器上，这会造成所有用户数据从旧分区迁移到新分区。正确的做法是对哈希的结果再分段，比如哈希结果的[0, 1000)属于第一个分区，[1000, 2000)属于第二个分区。示例图如下所示，取每个key的前两个字节计算md5，把结果按照范围划分到8个分区中：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/partitioning2.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种方式的优点是数据分布比较均匀，不容易产生性能热点。在实现的时候，为了让数据更加均匀，可以给每个服务器分配多个虚拟节点，每个虚拟节点负责一段范围，具体可以参考&lt;a href=&quot;https://zhuanlan.zhihu.com/p/98030096&quot;&gt;一致性哈希&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;缺点是没法进行范围查询，因为相邻的ID很难在同一个分区。一种折中方案是用key的一部分去计算hash得出分区，其余部分在分区内顺序存储。比如Cassandra设计了一个复合主键(compound primary key)，由多个列组成。其中第一列用于计算hash，其余列是按顺序保存在SSTable中。虽然对于不同的第一列值无法做范围查询，但是同一个key的其余列可以做范围查询。这种方式适合社交网站，一个用户发的帖子复合主键是(user_id, update_timestamp)，可以按照时间范围查询同一个用户的帖子。&lt;/p&gt;

&lt;p&gt;即使使用哈希方式，还是可能会遇到性能热点。比如微博，某个明星发了个帖子，可能有上百万的访问量和评论，这样还是会落在同一个分区上。一种解决方法是针对这个key再额外增加两个随机数字，这个key的评论进一步划分到100个分区上，减少热点。代价是查询的时候需要向这100个分区发送请求，合并后才能得到总数据。&lt;/p&gt;

&lt;h1 id=&quot;二级索引&quot;&gt;二级索引&lt;/h1&gt;

&lt;p&gt;上文介绍的分区方法都是在主键（关键字）基础上进行分区，如果业务用到了二级索引(secondary index)，如何处理？举个例子：一个卖二手车的网站，DB中主键是唯一ID，数据包含车的颜色、品牌和位置。如果要查询所有颜色是红色的车，如何实现？有两种方式，这两种方式都需要建立二级索引，也就是查询的列和主键的映射关系，区别在于二级索引的划分方式不一样。&lt;/p&gt;

&lt;h2 id=&quot;二级索引按照主键分区&quot;&gt;二级索引按照主键分区&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/partitioning-secondary1.png&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一种方式是建立color和主键的映射关系，而且这种映射关系保存在每个分区内部，只保留本分区的数据。比如上图中，分区0中红色的车有191和306两个，都是分区0中的数据；分区1中红色的车有768，是分区1中的数据，不会有分区0中的数据。&lt;/p&gt;

&lt;p&gt;这种方式查询二级索引需要把请求发到所有分区，因为每个分区都可能有对应的数据，比如每个分区中都有红色的车。&lt;/p&gt;

&lt;h2 id=&quot;二级索引按照其他键分区&quot;&gt;二级索引按照其他键分区&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/partitioning-secondary2.png&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第二种方式是二级索引按照其他键分区，而不是主键。比如上图中颜色按照字典序分区，[a-r]在分区0中，[s-z]在分区1中。&lt;/p&gt;

&lt;p&gt;这种方式的优点是查询二级索引不需要向所有分区发送请求，只要发到对应分区上就行了。&lt;/p&gt;

&lt;p&gt;缺点是可能出现数据不一致，因为二级索引和主键可能不在同一个分区，添加新数据的时候需要向两个分区发送请求，一个是主键分区，另一个是二级索引分区，可能出现部分更新成功的情况，这需要&lt;a href=&quot;https://yang.observer/2020/05/06/distributed-transaction/&quot;&gt;分布式事务&lt;/a&gt;来解决这个问题。&lt;/p&gt;

&lt;h1 id=&quot;分区再平衡&quot;&gt;分区再平衡&lt;/h1&gt;

&lt;p&gt;随着请求量的变化，分区的数量需要调整，请求量变多，分区数需要增加，请求量变少，分区数需要减少，这种调整叫做分区再平衡(partitioning rebalancing)。&lt;/p&gt;

&lt;p&gt;分区再平衡可能会遇到下面几个问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;再平衡后负载应该均匀分布。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;再平衡移动的数据应该尽可能的少和快，减少不必要的数据移动。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;再平衡过程中，服务器应该正常服务。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有两种再平衡方法。&lt;/p&gt;

&lt;h2 id=&quot;固定数量分区&quot;&gt;固定数量分区&lt;/h2&gt;

&lt;p&gt;这种方式预先分配多个分区，分区数量比当前物理机器数量多不少，可以按照业务最大的负载数预估。比如10个物理机器可以分配1000个分区，每个物理机器负责100个分区。新增机器时，修改物理机器和分区的映射关系，新机器从每个旧机器上分配一些分区，修改的时候注意保持均匀分布。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/rebalancing.png&quot; alt=&quot;图六&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图中一开始有4个物理机器，预先分配20个分区，每个机器负责5个分区。新加机器后，每个旧机器分配一个分区到新机器上，这样每个机器负责4个分区。&lt;/p&gt;

&lt;h2 id=&quot;动态分区&quot;&gt;动态分区&lt;/h2&gt;

&lt;p&gt;另外一种方式是动态分区。这种方式不需要预先分配分区数量，而是动态计算分区。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/98030096&quot;&gt;一致性哈希&lt;/a&gt;也算动态分区的一种实现方式。&lt;/p&gt;

&lt;h2 id=&quot;自动还是手动&quot;&gt;自动还是手动&lt;/h2&gt;

&lt;p&gt;再平衡应该自动处理还是手动处理？自动意思是程序自动检测是否需要再平衡，自动迁移分区到新的机器上。手动意思是管理员手动配置分区，手动发起再平衡流程。&lt;/p&gt;

&lt;p&gt;两种方式各有利弊。纯自动可能会引起额外的问题。比如一个机器过载了，程序自动检测后发起再平衡流程，把数据从过载机器迁移到新机器，但是这个迁移过程会造成更大的开销，引起雪崩。纯手动的问题是容易配错。可以考虑折中一下，能自动的地方自动化，发起再平衡前再人工审核一下。&lt;/p&gt;

&lt;h2 id=&quot;路由&quot;&gt;路由&lt;/h2&gt;

&lt;p&gt;分区后，客户端怎么知道数据在哪个分区，怎么把请求路由到正确的分区？有三种方式，如下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-03-13-ddia-partitioning/routing.png&quot; alt=&quot;图七&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;客户端随机找个节点，每个节点保存了当前的分区信息。如果这个节点刚好有对应数据，直接返回客户端，如果没有，把请求转发给对应的节点。&lt;/li&gt;
  &lt;li&gt;客户端把请求发给负载均衡服务器，这个服务器知道分区信息，转发请求给对应的节点。&lt;/li&gt;
  &lt;li&gt;客户端知道当前的分区信息，发请求到对应的服务器。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中最大的挑战在于&lt;strong&gt;如何让每个节点都知道分区信息的变化，让所有节点产生共识(consensus)&lt;/strong&gt;。一种方式是将分区信息放在ZooKeep上。但是如何实现？再平衡过程中如何保持服务的正常运行？如何保证数据迁移前后的一致性？&lt;strong&gt;数据迁移&lt;/strong&gt;是一个比较麻烦的问题，有时间再专门研究。&lt;/p&gt;

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

&lt;p&gt;&lt;a href=&quot;https://book.douban.com/subject/26197294/&quot;&gt;Designing Data-Intensive Applications&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sun, 13 Mar 2022 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2022/03/13/ddia-partitioning/</link>
        <guid isPermaLink="true">https://yang.observer/2022/03/13/ddia-partitioning/</guid>
        
        <category>DDIA</category>
        
        <category>partitioning</category>
        
        <category>rebalancing</category>
        
        <category>分区</category>
        
        <category>再平衡</category>
        
        
      </item>
    
      <item>
        <title>复制</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#主从复制&quot; id=&quot;markdown-toc-主从复制&quot;&gt;主从复制&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#同步复制和异步复制&quot; id=&quot;markdown-toc-同步复制和异步复制&quot;&gt;同步复制和异步复制&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#添加新副本&quot; id=&quot;markdown-toc-添加新副本&quot;&gt;添加新副本&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#处理副本崩溃&quot; id=&quot;markdown-toc-处理副本崩溃&quot;&gt;处理副本崩溃&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#复制的实现&quot; id=&quot;markdown-toc-复制的实现&quot;&gt;复制的实现&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#复制的延迟问题&quot; id=&quot;markdown-toc-复制的延迟问题&quot;&gt;复制的延迟问题&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#多主复制&quot; id=&quot;markdown-toc-多主复制&quot;&gt;多主复制&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#写冲突处理&quot; id=&quot;markdown-toc-写冲突处理&quot;&gt;写冲突处理&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#多主复制拓扑&quot; id=&quot;markdown-toc-多主复制拓扑&quot;&gt;多主复制拓扑&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#无主复制&quot; id=&quot;markdown-toc-无主复制&quot;&gt;无主复制&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#处理并发写&quot; id=&quot;markdown-toc-处理并发写&quot;&gt;处理并发写&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考&quot; id=&quot;markdown-toc-参考&quot;&gt;参考&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最近在看&lt;a href=&quot;https://book.douban.com/subject/26197294/&quot;&gt;《Designing Data-Intensive Applications》&lt;/a&gt;，本文主要记录书中第五章《复制》的内容，加深自己的理解。&lt;/p&gt;

&lt;p&gt;复制(Replication)是通过网络把一份数据保存在多个机器上。为什么需要复制数据？原因主要有三个：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;把数据放在离用户近的位置，减少网络延迟。&lt;/li&gt;
  &lt;li&gt;某台机器崩溃后，其他具有相同数据的机器可以继续服务，增加可用性。&lt;/li&gt;
  &lt;li&gt;把读请求分散在不停机器上，增加读吞吐量。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;通常有三种复制方案：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;主从复制(Leaders and Followers)&lt;/li&gt;
  &lt;li&gt;多主复制(Multi-Leader Replication)&lt;/li&gt;
  &lt;li&gt;无主复制(Leaderless Replication)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;主从复制&quot;&gt;主从复制&lt;/h1&gt;

&lt;p&gt;主从复制的流程如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;指定某个副本作为主副本(Leader)。写数据时，请求必须先发到主副本。&lt;/li&gt;
  &lt;li&gt;其他副本作为从副本(Follower)。主副本写了本地数据后，把数据的变化发给从副本，从副本更新本地数据。&lt;/li&gt;
  &lt;li&gt;客户端读数据可以查询主副本或者从副本，但是写只能发给主副本。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;示例图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/leader-follower1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;同步复制和异步复制&quot;&gt;同步复制和异步复制&lt;/h2&gt;

&lt;p&gt;主从复制中，主副本会把数据的变化发给从副本复制。这种复制有两种方式，分为同步(Synchronous)复制和异步(Asynchronous)复制。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/leader-follower2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图中，Leader有两个Follower 1和2。Follower1使用&lt;strong&gt;同步复制&lt;/strong&gt;，Follower2使用&lt;strong&gt;异步复制&lt;/strong&gt;。Leader收到用户的更新请求后，把数据的变化发给Follower1和Follower2。因为Follower1使用同步复制，Leader等待Follower1返回成功后才会给用户返回成功，而Follower2使用异步复制，Leader不会等待Follower2返回结果。&lt;/p&gt;

&lt;p&gt;同步复制的优点在于follower上的数据是最新的。如果leader崩溃，follower可以继续服务，不会有数据丢失。缺点在于如果某个follower出故障没有响应，leader就会一直阻塞，导致整个系统无法工作。&lt;/p&gt;

&lt;p&gt;异步复制不存在某个follower故障阻塞整个系统的问题。但是异步复制会存在数据丢失的问题，如果follower没有成功复制数据，leader先给用户返回成功，然后leader崩溃了，这时follower上并没有最新的数据。&lt;/p&gt;

&lt;p&gt;权衡这两种情况，得出了半同步(semi-synchronous)的解决方案。半同步的方案是某一个follower使用同步复制，其他follower使用异步复制。如果同步的follower故障了，把剩余的一个follower从异步复制设置为同步方式。这样可以保证至少有一个副本上的数据是最新的。&lt;/p&gt;

&lt;h2 id=&quot;添加新副本&quot;&gt;添加新副本&lt;/h2&gt;

&lt;p&gt;主从复制方式中，如何添加新的副本？不能直接把已有副本的数据拷贝到新副本，因为客户端还在持续不断地写入新的请求。常用的方案如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在leader上保存&lt;a href=&quot;https://yang.observer/2021/11/27/distributed-snapshots/&quot;&gt;全局一致性快照(snapshot)&lt;/a&gt;和快照时间点。&lt;/li&gt;
  &lt;li&gt;把快照发到新副本上。&lt;/li&gt;
  &lt;li&gt;新副本连到leader，让leader发送从快照时间点之后的所有数据变化历史。&lt;/li&gt;
  &lt;li&gt;新副本处理完所有变化历史后数据就和leader保持一致了。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;处理副本崩溃&quot;&gt;处理副本崩溃&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;从副本故障&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;follower可以在本地记录已经复制的历史数据位置，比如时间戳或者下标，崩溃后重启，因为有历史数据位置信息，可以向leader请求同步从这个位置开始的数据变化。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;主副本故障&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;主副本故障的处理相对麻烦一些，流程如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;检测leader故障。如果leader超过一定时间没有心跳包，认为leader出现故障。&lt;/li&gt;
  &lt;li&gt;选出新leader。可以通过多数派选举或者指派的方式选出新leader。新leader应该是具有最新数据的副本。&lt;/li&gt;
  &lt;li&gt;配置整个系统使用新leader。客户端需要把请求发给新leader，其他follower需要从新leader复制数据。如果旧leader恢复上线，整个系统应该拒绝承认旧leader，否则系统里面有两个leader，存在脑裂问题(split brain)。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中包含了分布式系统的本质问题：节点失效，不可靠的网络以及一致性、持久性、可用性和延迟之间的权衡。&lt;/p&gt;

&lt;h2 id=&quot;复制的实现&quot;&gt;复制的实现&lt;/h2&gt;

&lt;p&gt;前面说了复制的时候，leader会把数据的变化发给follower，这个具体是怎么实现的？有四种方案。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;基于语句的复制&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;以DB为例，这种方案中，leader记录客户端的sql语句，发给follower，follower执行每条语句。MySQL 5.1之前的版本使用了这种方式。&lt;/p&gt;

&lt;p&gt;这种方案有几个问题：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;sql语句的结果可能不是固定的。比如now(), rand()等函数每次执行的结果可能不一样。&lt;/li&gt;
  &lt;li&gt;sql语句使用自增长字段的值不同副本可能不一样。&lt;/li&gt;
  &lt;li&gt;sql语句如果有副作用，比如触发器、存储过程等，这些副作用的结果在不同副本上可能不一样。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Write-ahead log(WAL)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在&lt;a href=&quot;https://yang.observer/2021/12/04/ddia-storage-engine/&quot;&gt;《存储引擎漫话》&lt;/a&gt;中介绍过WAL。WAL中按顺序保存了数据的修改。leader可以在写入WAL 后，把它发给follower，follower解析WAL，在本地处理数据的变化。PostgreSQL和Oracle使用了这种方式。&lt;/p&gt;

&lt;p&gt;这种方案的缺点是WAL比较底层，里面可能包含哪个磁盘块的哪个字节被改变了，这样就让复制的过程和存储引擎耦合了，如果存储格式变了，leader和follower的程序版本不一样了，可能就无法处理。这可能会影响程序的升级，比如我们要升级整个系统，可以先升级follower，然后把follower升级为leader，这样可以无缝升级整个系统。但是如果WAL格式无法兼容新旧版本就无法做到无缝升级。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;基于逻辑的复制&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;为了解决WAL太过底层导致无法兼容的问题，我们不使用存储引擎用的WAL或者叫物理(physical)日志，改用逻辑(logical)日志。这种逻辑日志也叫基于行的日志(row-based)。MySQL的binlog使用了这种方式。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;插入时，逻辑日志包含插入的所有列数据&lt;/li&gt;
  &lt;li&gt;删除时，逻辑日志包含被删除行的唯一标识。&lt;/li&gt;
  &lt;li&gt;更新时，逻辑日志包含行的唯一标识和变化的数据。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这种方式解偶了存储引擎和复制功能，而且逻辑日志可以发给外部系统用于离线处理。&lt;/p&gt;

&lt;h2 id=&quot;复制的延迟问题&quot;&gt;复制的延迟问题&lt;/h2&gt;

&lt;p&gt;主从复制中，写请求由leader处理，读请求由任何一个副本处理，这样可以把读请求分散在不同副本上，降低leader的负载，但是这会导致一致性的问题。在异步复制或者半同步复制的方案下，客户端发起写请求，leader给客户端返回成功后，客户端向follower发起读请求，这时leader的数据可能还没有复制到这个follower，这时就产生了一致性问题。常见的一致性问题有三类。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;读自己的写操作&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/lag1.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，异步复制下，用户先发送写请求，leader给用户返回成功，同时给两个follower发送同步请求，之后用户给Follower 2发送读请求，但这时leader的同步请求还没到Follower 2，这就是写后读不一致(read-after-write inconsistency)。常用的解决方案有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;从leader上读用户自己的数据，从follower上读其他人的数据。比如社交程序，一般自己的用户数据只能自己修改，别人的数据只能查看。这种情况下，如果是读自己的数据，可以发到leader，如果是读其他人的数据，可以发到follower。&lt;/li&gt;
  &lt;li&gt;网关程序记录每个用户数据的更新时间，如果当前时间在更新时间的1分钟内，把读请求发给leader，否则发给follower，也就是认为延迟最长是1分钟。也可以记录每个follower上的复制延迟，这样更精确。&lt;/li&gt;
  &lt;li&gt;客户端记录写请求的时间戳，发请求给follower的时候带上这个时间戳，如果follower发现本地数据的时间戳不够新，把请求转发给leader。这种方案需要注意用户在多个终端登录的情况，不同终端上需要同步相同的本地写请求的时间戳，而且要注意不同终端的网络连接可能不一样，导致路由到不同follower。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;单调读&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/lag2.png&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，User1234插入了一条数据，leader把数据同步给两个Follower，Follower1先收到，Follower2过了一段时间才收到。User2345先在Follower1上查询到了这条数据，然后又查询了一次，这次的请求落在了Follower2上，但是Follower2还没同步到这条数据，这就是单调读问题(Monotonic Read)，也就是读数据的时候，出现了数据回退的情况。&lt;/p&gt;

&lt;p&gt;解决方案是同一个用户的读请求应该落在同一个副本上，可以通过对用户ID做哈希的方式来实现。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一致性前缀读&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/lag3.png&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一致性前缀读(Consistent Prefix Read)也叫因果一致性，如图所示，Mr. Poons和Mrs. Cake有如下对话。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;Mr. Poons
    How far into the future can you see, Mrs. Cake?
Mrs. Cake
    About ten seconds usually, Mr. Poons.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Mr. Poons先问了一个问题，Mrs. Cake回答了问题。Mr. Poons和Mrs. Cake在不同的分区(partition)，每个分区都有自己的leader。此时有第三个观察者在看他们的对话，由于网络延迟，Partition2先把回答发给了观察者，之后Partition1才把问题发给了观察者，这违反了因果关系，应该先看到问题再看到回答。这个问题是因为不同用户在不同的分区，如果大家都在同一个分区不会有这个问题。在&lt;a href=&quot;https://yang.observer/2020/09/12/vector-clock/#%E5%90%91%E9%87%8F%E6%97%B6%E9%92%9F%E7%9A%84%E5%BA%94%E7%94%A8&quot;&gt;计算机的时钟（三）：向量时钟&lt;/a&gt;中也讨论过相似的例子。一种解决方案是文中提到的向量时钟，这里不再重复描述。&lt;/p&gt;

&lt;h1 id=&quot;多主复制&quot;&gt;多主复制&lt;/h1&gt;

&lt;p&gt;上面介绍的主从复制只有一个leader，另外一种方案是多主复制(multi-leader replication)，也就是系统中有多个leader。常用的多主复制场景如下。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;多中心操作&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;为了提升系统的可用性（比如灾备），或者降低用户访问延迟（比如全球部署），系统可能会部署在多个数据中心。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/multi-leader1.png&quot; alt=&quot;图六&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;客户端离线操作&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;比如一个日历程序，可能会在手机，笔记本和台式机上访问。如果在离线的时候修改了日历内容，下次联网的时候需要把数据同步到服务器，这种情况下每个设备都是一个leader或者说是一个数据中心。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;协作编辑&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在线实时协作编辑软件，比如Google Docs，每个用户在本地浏览器中编辑，然后数据同步到服务器上，这个和客户端离线操作比较类似。&lt;/p&gt;

&lt;h2 id=&quot;写冲突处理&quot;&gt;写冲突处理&lt;/h2&gt;

&lt;p&gt;多主复制最大的一个问题是写冲突，多个leader同时接受了写入同一个id的请求就会造成写冲突。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/multi-leader2.png&quot; alt=&quot;图七&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，两个用户连接了两个不同的leader，同时修改了同一个id的数据，造成了冲突。&lt;/p&gt;

&lt;p&gt;处理冲突的一种方式是避免冲突(Conflict avoidance)。比如不同ID的写请求路由到不同的leader上。但是这样可能失去了多主复制的优势，使用多主的目的是为了提升可用性或者降低延迟，如果把数据分配在不同leader上，如果某个leader崩溃，多主复制就没有用了。&lt;/p&gt;

&lt;p&gt;另外一种方式是一致性状态收敛(converging toward a consistent state)。在主从复制中，写操作是顺序发生的，不会产生冲突。多主复制中，由于写操作可能同时发生，需要解决冲突，让数据收敛到一致的状态。常用的方案有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;last write wins(LWW)，给每个写请求分配唯一ID，发生冲突时，保留ID最大的写操作，丢弃其他并发写操作。&lt;/li&gt;
  &lt;li&gt;给每个副本分配唯一ID，发生冲突时，保留ID最大的副本上的写操作，丢弃其他副本的写操作。&lt;/li&gt;
  &lt;li&gt;同时写入冲突的数据，并且记录冲突状态，之后由用户来解决冲突(比如弹窗让用户选择数据)。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;多主复制拓扑&quot;&gt;多主复制拓扑&lt;/h2&gt;

&lt;p&gt;多主复制中leader之间需要复制数据，常用的复制拓扑结构有三种：环形，星型和全连接。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/multi-leader3.png&quot; alt=&quot;图八&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于环形和星型，可能出现某个副本故障导致整个复制失败的情况。&lt;/p&gt;

&lt;p&gt;对于全连接，可能会出现上面介绍过的因果一致性问题。如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/multi-leader4.png&quot; alt=&quot;图九&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;无主复制&quot;&gt;无主复制&lt;/h1&gt;

&lt;p&gt;主从复制和多主复制这两种方案中，客户端的写请求都是发给某一个leader，只由这个leader处理这个请求，然后再复制到其他副本。第三种方案叫做无主复制(leaderless replication)，这种方案中客户端把写请求同时发给所有副本，副本之间没有主从之分。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/leaderless1.png&quot; alt=&quot;图十&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，客户端写入时，把请求同时发给3个副本，收到2个副本的成功响应，第3个副本故障了不影响这次的请求，只要有多数派返回成功就认为这次写入成功。那第3个副本重启后，缺少了这次写请求的数据，怎么同步这次数据的改动？有两种办法。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;读修复：客户端读数据的时候，向所有副本发请求，如上图所示，这时副本1和副本2返回了最新的数据，版本号是7，而副本3返回的是旧数据，版本号是6，这时客户端向副本3发一次写请求，更新副本3上的数据。&lt;/li&gt;
  &lt;li&gt;后台进程修复：有个后台进程定时扫描副本之间数据的差异，同步最新的数据。这个方案的好处是可以同步所有数据，读修复方案只能修复热数据，冷数据可能要很久才有机会修复。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;选择写或者读的成功数量时，有个条件: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w + r &amp;gt; n&lt;/code&gt;，其中n为总副本个数，w为写请求至少收到成功响应的个数，r为读请求至少收到成功响应的个数。只要满足&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w + r &amp;gt; n&lt;/code&gt;就可保证一定能读到最新的数据。一般n会设置成奇数，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w = r = (n + 1) / 2&lt;/code&gt;，这样可以容忍 n / 2 个副本故障。根据业务的情况，可以修改w和r。比如对于写很少，读很多的业务，可以设置&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w = n, r = 1&lt;/code&gt;，不过这样不能容忍写入时有副本故障。&lt;/p&gt;

&lt;p&gt;无主复制适合对可用性和低延迟要求比较高，对一致性或者读到旧数据要求不高的场景。&lt;/p&gt;

&lt;h2 id=&quot;处理并发写&quot;&gt;处理并发写&lt;/h2&gt;

&lt;p&gt;无主复制最大的一个问题是并发写冲突。举个例子：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/leaderless2.png&quot; alt=&quot;图十一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;客户端A和B分别向系统发送写请求，A 设置X = A, B 设置 X = B。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Node 1 只收到了A请求。&lt;/li&gt;
  &lt;li&gt;Node 2 先收到A，后收到B。&lt;/li&gt;
  &lt;li&gt;Node 3 先收到B，后收到A。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这时系统数据不一致，读取X的时候，有两个节点返回A，一个节点返回B。我们需要实现最终一致性，上面介绍了多主复制方案下写冲突的处理方法，这里也可以使用last write win(LWW)方法来处理。&lt;/p&gt;

&lt;p&gt;另外一种方法是构造”happend befefore”关系。举个例子，两个用户同时往同一个购物车添加商品。如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/leaderless3.png&quot; alt=&quot;图十二&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Client 1第一次把milk加到购物车中，没有带版本号，服务器成功处理，返回更新后的数据和当前版本号1。&lt;/li&gt;
  &lt;li&gt;Client 2第一次把eggs加到购物车中，没有带版本号，服务器发现数据版本号已经是1了，而且客户端没有带版本号过来，服务器把[eggs]和[milk]分别作为两个版本保存，升级版本号为2，把[eggs]和[milk]和版本号返回Client 2。&lt;/li&gt;
  &lt;li&gt;Client 1想把flour加到购物车，在第1步的返回结果中已经知道版本号是1，所以发送购物车内容为[milk, flour]，带上版本号1。服务器发现Client 1带过来的版本号是1，说明Client 1是在版本号1基础上做的写入，可以安全把版本号1的内容替换为[milk, flour]，而[eggs]属于版本2，大于Client 1带来的版本号1，所以是并发操作，需要分开存储，[milk, flour]版本号升级为3，[egg]的版本号还是为2。把[milk, flour]和[eggs]，以及最新版本号3返回Client 1。&lt;/li&gt;
  &lt;li&gt;Client 2想把ham加到购物车，在第2步的返回结果中已经知道版本号是2，购物车中已经有[milk]和[eggs]，它需要做合并，同时把ham加入，发送内容为[eggs, milk, ham]，带上版本号2。服务发现Client 2带过来的版本号是2，说明Client 2是在版本号2基础上做的写入，可以安全把版本号2的内容替换为[eggs, milk, ham]，而[milk, flour]的版本号是3，大于Client 2带来的版本号2，所以是并发操作，需要分开存储，[eggs, milk, ham]版本号升级为4，[milk, flour]的版本还还是3。把[eggs, milk, ham]和[milk, flour]，以及最新版本号4返回Client 2。&lt;/li&gt;
  &lt;li&gt;Client 1想把bacon加到购物车，在第3步返回结果中已经知道版本号是3，购物车中已经有[milk, flour]和[egg]，它需要做合并，同时把bacon加入，发送内容为[milk, flour, eggs, bacon]，带上版本号3。服务器替换版本号3的内容为[milk, flour, eggs, bacon]，升级版本号为5，版本号4的内容还是[milk, eggs, ham]，把[milk, flour, eggs, bacon]和[eggs, milk, ham]以及最新版本号5返回给Client 1。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;整个过程中服务器收到了客户端带来的版本号，可以安全的替换小于等于这个版本号的数据，这属于happened before，需要保留比这个版本号大的数据，这属于concurrent。下图描述了整个过程中的happened before和concurrent关系。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2022-02-26-ddia-replication/leaderless4.png&quot; alt=&quot;图十三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于多副本的情况，每个副本需要保存自己的版本号，同时需要记录其他副本的版本号，这个算法和&lt;a href=&quot;https://yang.observer/2020/09/12/vector-clock/#%E5%90%91%E9%87%8F%E6%97%B6%E9%92%9F%E7%9A%84%E5%BA%94%E7%94%A8&quot;&gt;向量时钟&lt;/a&gt;有点像，叫做版本向量(version vector)，这里不做过多介绍。&lt;/p&gt;

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

&lt;p&gt;&lt;a href=&quot;https://book.douban.com/subject/26197294/&quot;&gt;Designing Data-Intensive Applications&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 26 Feb 2022 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2022/02/26/ddia-replication/</link>
        <guid isPermaLink="true">https://yang.observer/2022/02/26/ddia-replication/</guid>
        
        <category>DDIA</category>
        
        <category>replication</category>
        
        
      </item>
    
      <item>
        <title>存储引擎漫话</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#最简单的数据库&quot; id=&quot;markdown-toc-最简单的数据库&quot;&gt;最简单的数据库&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#哈希索引&quot; id=&quot;markdown-toc-哈希索引&quot;&gt;哈希索引&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#sstable和lsm-tree&quot; id=&quot;markdown-toc-sstable和lsm-tree&quot;&gt;SSTable和LSM-Tree&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#b-tree&quot; id=&quot;markdown-toc-b-tree&quot;&gt;B-Tree&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#b-tree和lsm-tree比较&quot; id=&quot;markdown-toc-b-tree和lsm-tree比较&quot;&gt;B-Tree和LSM-Tree比较&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#二级索引&quot; id=&quot;markdown-toc-二级索引&quot;&gt;二级索引&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考&quot; id=&quot;markdown-toc-参考&quot;&gt;参考&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最近在看&lt;a href=&quot;https://book.douban.com/subject/26197294/&quot;&gt;《Designing Data-Intensive Applications》&lt;/a&gt;，本文主要记录书中第三章存储和检索的内容，加深自己的理解。&lt;/p&gt;

&lt;h1 id=&quot;最简单的数据库&quot;&gt;最简单的数据库&lt;/h1&gt;

&lt;p&gt;什么是世界上最简单的数据库？只需要两个bash函数：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;#!/bin/bash
db_set () {
    echo &quot;$1,$2&quot; &amp;gt;&amp;gt; database
}
db_get () {
    grep &quot;^$1,&quot; database | sed -e &quot;s/^$1,//&quot; | tail -n 1
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;$ db_set 123456 &apos;{&quot;name&quot;:&quot;London&quot;,&quot;attractions&quot;:[&quot;Big Ben&quot;,&quot;London Eye&quot;]}&apos; 
$ db_set 42 &apos;{&quot;name&quot;:&quot;San Francisco&quot;,&quot;attractions&quot;:[&quot;Golden Gate Bridge&quot;]}&apos;
$ db_get 42
{&quot;name&quot;:&quot;San Francisco&quot;,&quot;attractions&quot;:[&quot;Golden Gate Bridge&quot;]}
$ db_set 42 &apos;{&quot;name&quot;:&quot;San Francisco&quot;,&quot;attractions&quot;:[&quot;Exploratorium&quot;]}&apos; 
$ db_get 42
{&quot;name&quot;:&quot;San Francisco&quot;,&quot;attractions&quot;:[&quot;Exploratorium&quot;]}
$ cat database
123456,{&quot;name&quot;:&quot;London&quot;,&quot;attractions&quot;:[&quot;Big Ben&quot;,&quot;London Eye&quot;]} 
42,{&quot;name&quot;:&quot;San Francisco&quot;,&quot;attractions&quot;:[&quot;Golden Gate Bridge&quot;]} 
42,{&quot;name&quot;:&quot;San Francisco&quot;,&quot;attractions&quot;:[&quot;Exploratorium&quot;]}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个最简单的数据库底层存储文件是一个文本文件”database”，每行是用逗号分隔的key和value。每次调用db_set，把key和value追加到dababase文件中。每次调用db_get，查询的是对应key的最后一次写入记录。&lt;/p&gt;

&lt;p&gt;这个数据库具备了存储和查询这两种最基本的数据库功能。存储使用了append操作，而不是随机写。对于写操作，append是最快的操作。对于读操作，使用的是线性查找，复杂度O(n)。随着数据量的增加，查找所花的时间也会线性增长。&lt;/p&gt;

&lt;p&gt;为了提升查询性能，可以给数据增加索引。索引可以显著地提升查询性能，但同时也会降低写入性能，因为写入时除了要写入数据本身，还要更新索引，所以需要权衡如何高效地建立索引。&lt;/p&gt;

&lt;h1 id=&quot;哈希索引&quot;&gt;哈希索引&lt;/h1&gt;

&lt;p&gt;一种直观地建立索引的方法是使用哈希表索引(Hash Indexes)。我们可以在内存中建立一个哈希表，哈希表的key是数据的key，哈希表的value是这个key对应的数据所在的文件的偏移。举个例子，上文123456和42这两个key的索引如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-12-04-ddia-storage-engine/hash-index1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://riak.com/assets/bitcask-intro.pdf&quot;&gt;Bitcask存储引擎&lt;/a&gt;使用的就是这种方式。Bitcask存储引擎写入时对文件执行的是追加(append)操作，查询的时候通过建立哈希索引加快查询性能。哈希表保存在内存中。这种存储引擎比较适合key的数量有限，并且写操作比较频繁的场景。Bitcask存储引擎遇到的问题和解决方案如下。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;数据文件只有append操作，磁盘早晚会有耗尽的一天，怎么解决这个问题？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;解决方案是写入数据的时候，把数据文件分成固定的大小，一个数据文件达到大小后，写入新的文件中。然后对已经写入的文件进行压缩(compaction)操作。因为同一个key的操作可能有很多次，只有最后一次操作的值才有意义，之前操作的记录没有存在的必要，所以我们可以遍历已经保存过的文件，只保留每个key的最后一次操作值，然后把这些最新的值写入新的数据文件中，老的数据文件就可以删除了。压缩完了还需要更新哈希表中key对应的文件偏移。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-12-04-ddia-storage-engine/hash-index2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图中yawn, scratch, memw, purr这四个key在老的数据文件中出现了多次，经过压缩后，数据减少了很多。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;哈希表只保存在内存中，进程重启或者崩溃的时候怎么办？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一种解决方法是进程重启的时候，扫描所有的数据文件，在内存中重建哈希表。这个过程可能很耗时间，所以Bitcask使用的优化方式是把哈希表也保存在磁盘中，重启的时候可以加载磁盘的哈希表，快速建立索引。当然这样会降低写入的性能，因为写入数据的时候需要把哈希索引表也写入磁盘中，需要权衡。&lt;/p&gt;

&lt;p&gt;哈希索引也有缺点，主要有两个：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果key的数量很多，内存不够大，无法把所有的key都保存在内存中。&lt;/li&gt;
  &lt;li&gt;范围查询效率不高。比如需要查询kitty00000到kitty99999这个范围的数据，必须先到哈希表中查询每个key的文件偏移位置，再去读磁盘。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;sstable和lsm-tree&quot;&gt;SSTable和LSM-Tree&lt;/h1&gt;

&lt;p&gt;既然哈希索引范围查询效率不高，有没有优化方法呢？&lt;/p&gt;

&lt;p&gt;有一种解决方案是使用Sorted String Table(SSTable)。上文介绍Bitcask存储引擎的数据分成了一个个固定大小的文件，每个文件中key的顺序是不固定的。SSTable的区别在于每个数据文件是根据key排过序的。这是如何做到的呢？&lt;/p&gt;

&lt;p&gt;首先，数据写入的时候先把数据保存在内存中的平衡树结构中（比如红黑树或者AVL树）。这种内存树结构也被叫做memtable。&lt;/p&gt;

&lt;p&gt;当memtable的大小超过一定值时（比如几MB），把当前memtable写入磁盘的SSTable文件中。因为平衡树本身是有序的，所以把它们写入磁盘的时候也可以保持顺序写入。新的写请求会写入到内存中新的memtable。&lt;/p&gt;

&lt;p&gt;和Bitcask存储引擎一样，SSTables方案里有一个后台压缩进程持续地对已经生成的SSTable文件进行合并。合并的方式可以采用归并排序(mergesort)算法，这样合并后的SSTable文件也是有序的。排序过程中，如果一个key在多个文件中出现，只需要使用最新的文件中的数据。如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-12-04-ddia-storage-engine/sstable1.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用SSTables结构，查询数据时，首先到内存memtable中查询数据是否存在，如果不存在，再到磁盘的SSTable文件中查找。对于SSTable文件，内存中也有key的索引表，区别在于我们不需要在内存中保存所有的key。因为SSTable中数据是有序的，我们只需要保存少数几个key的文件位置偏移就行了。如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-12-04-ddia-storage-engine/sstable2.png&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于key的范围为handbag和handprinted，内存中只需要保存handbag的位置偏移，查找key时先查找和这个key最相近的key所对应的位置偏移，然后读取整个偏移范围的内容，对这个范围内的数据再查找我们想要查的key。以handiwork为例，查找这个key时，先在内存索引表中找到最近的key为handbag，然后从SSTable文件中对应偏移位置开始读取对应长度的数据，然后扫描这段范围的数据，找到handiwork的值。一般来说内存索引表中一个key对应几KB的数据，所以扫描很快。另外保存这段范围的数据时，可以压缩之后再写入磁盘，这样增加磁盘IO的效率，不过这也带来了CPU压缩和解压缩的开销。比如handbag到handprinted这个范围的数据可以先压缩后再保存在磁盘中。&lt;/p&gt;

&lt;p&gt;上文说到查询数据时先到memtable中查找key是否存在，不存在再到最新的SSTable文件中查找，然后再到旧的SSTable文件中查找，一直到最老的SSTable文件。如果数据库中根本不存在这个key的话，查找过程可能会比较耗时间，一种优化方式是使用&lt;a href=&quot;https://en.wikipedia.org/wiki/Bloom_filter&quot;&gt;布隆过滤器(Bloom filter)&lt;/a&gt;来判断key是否不存在，key存在的话再执行上面的查找流程。&lt;/p&gt;

&lt;p&gt;SSTable方案内存中保存了memtable，如果进程重启或者崩溃怎么处理？一种方案是内存写入memtable前，磁盘先写入一个log文件，这个log文件只有append操作。进程重启的时候，使用这个log文件重建memtable。每次内存中的memtable写入SSTable文件后，这个log文件也可以删除了。&lt;/p&gt;

&lt;p&gt;整个方案写入数据的时候写入memtable和append-only log文件中，所以写入性能很高，同时SSTable文件是有序的，进行范围查询的时候性能也很高。&lt;/p&gt;

&lt;p&gt;方案最早是在&lt;a href=&quot;https://www.cs.umb.edu/~poneil/lsmtree.pdf&quot;&gt;The Log-Structured Merge-Tree (LSM-Tree)&lt;/a&gt;这篇论文中描述，所以也叫LSM-Tree。很多数据库都使用了LSM-Tree，包括LevelDB、RocksDB、Cassandra、HBase等。&lt;/p&gt;

&lt;h1 id=&quot;b-tree&quot;&gt;B-Tree&lt;/h1&gt;

&lt;p&gt;虽然LSM-Tree近来发展很快，B-Tree仍然是目前使用最广泛的索引结构。&lt;/p&gt;

&lt;p&gt;B-Tree也是一种有序的数据结构，可以高效地进行范围查找。示例如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-12-04-ddia-storage-engine/btree1.png&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;p&gt;B-Tree中，每个数据文件的大小是固定，一般为4K或者更大，称作块(block)或者页(page)。B-Tree是分层结构。最上层的页作为根页。根页中按顺序保存了一些key，同时保存了指向子页的指针，也就是子页数据文件的位置。每个子页保存了一段范围的key和对应的位置指针。最底层的称做叶子页(leaf page)，叶子页保存了所有的数据，也有实现方式是在叶子页中只保存数据的指针。每页中保存的指针数量叫做分支系数(branching factor)，一般是几百个。大部分数据库B-Tree的层数是3到4层。对于4层的B-Tree，如果页是4KB，分支系数是500的话，可以储存256TB数据。&lt;/p&gt;

&lt;p&gt;B-Tree添加key的时候，需要找到包含这个key的页，如果这个页已经满了，需要分裂成两个新的页。示例如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-12-04-ddia-storage-engine/btree2.png&quot; alt=&quot;图六&quot; /&gt;&lt;/p&gt;

&lt;p&gt;B-Tree修改页的时候也面临进程崩溃的问题。为了解决这个问题，写入的时候会先写入一个append-only log文件中，也叫做write-ahead log(WAL)或者&lt;a href=&quot;https://yang.observer/2020/05/06/distributed-transaction/#redo%E6%97%A5%E5%BF%97&quot;&gt;redo log&lt;/a&gt;。WAL写入成功后再写入数据页，如果进程崩溃了，WAL可以用来恢复数据页。&lt;/p&gt;

&lt;h1 id=&quot;b-tree和lsm-tree比较&quot;&gt;B-Tree和LSM-Tree比较&lt;/h1&gt;

&lt;p&gt;我们来比较一下B-Tree和LSM-Tree。一般来说，LST-Tree写入速度更快，而B-Tree查询速度更快。LSM-Tree查询慢一些的原因是因为需要查询几次才能在对应的SSTable中找到数据。不过究竟哪个更合适还是需要根据自己业务数据的性能测试结果来定。&lt;/p&gt;

&lt;p&gt;B-Tree写入时需要至少两次IO操作，第一次写入WAL，第二次写入数据页。LSM-Tree执行写入操作的时候只需要写入一次WAL，另外一次是内存操作。但是因为后台有个压缩进程会反复对数据进行合并和压缩，一个数据会反复写入多次，这对SSD不太友好，因为SSD的写入次数是有限的。另外后台压缩进程运行的时候会占据大量磁盘IO，可能会影响正在执行的写入操作。随着数据文件的增大，压缩占据的磁盘IO也会更多，如果同时写入操作也很多的话，有可能压缩操作的速度赶不上写入操作的速度，导致磁盘空间被用光。&lt;/p&gt;

&lt;p&gt;LSM-Tree更容易被压缩，所以数据文件比B-Tree要小一些。而且B-Tree数据页可能不是完全利用的，比如一个页分裂成两个页的时候，这两个页都只有一半空间被使用了，LSM-Tree不存在这个问题。&lt;/p&gt;

&lt;h1 id=&quot;二级索引&quot;&gt;二级索引&lt;/h1&gt;

&lt;p&gt;我们知道很多数据库都可以建立二级索引，它的实现方式和一级索引一样，也可以使用B-Tree或者LSM-Tree。这里会遇到的问题主要是数据本身存在哪里？&lt;/p&gt;

&lt;p&gt;一种方式是数据存在每个索引中，这样会造成数据重复，有几个索引就会有几份数据，更新数据的时候会比较麻烦，需要更新多份数据，可能会导致一致性问题。这种方式也叫聚集索引(clustered index)，MySQL InnoDB的主键使用了这种方式。&lt;/p&gt;

&lt;p&gt;另外一种方式是数据只有一份，所有索引中不保存数据，只保存数据的指针。更新数据的时候如果数据长度没有变大的话，只需要更新一次。如果数据变大的话，可能需要把数据保存在新的地方，所有索引中的数据指针都需要更新。MySQL InnoDB中的二级索引使用了类似的方式，没有保存数据，保存的是主键中的数据指针。&lt;/p&gt;

&lt;p&gt;还有一种折中方案是在索引文件中只保存部分数据列，而不是整行，这也称作覆盖索引(covering index)。&lt;/p&gt;

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

&lt;p&gt;&lt;a href=&quot;https://book.douban.com/subject/26197294/&quot;&gt;Designing Data-Intensive Applications&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cs.umb.edu/~poneil/lsmtree.pdf&quot;&gt;The Log-Structured Merge-Tree (LSM-Tree)&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 04 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/12/04/ddia-storage-engine/</link>
        <guid isPermaLink="true">https://yang.observer/2021/12/04/ddia-storage-engine/</guid>
        
        <category>DDIA</category>
        
        <category>hash</category>
        
        <category>LSM-Tree</category>
        
        <category>B-Tree</category>
        
        <category>SSTable</category>
        
        <category>index</category>
        
        <category>database</category>
        
        
      </item>
    
      <item>
        <title>分布式系统的全局快照</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#问题描述&quot; id=&quot;markdown-toc-问题描述&quot;&gt;问题描述&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#chandy-lamport算法&quot; id=&quot;markdown-toc-chandy-lamport算法&quot;&gt;Chandy-Lamport算法&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#前提条件&quot; id=&quot;markdown-toc-前提条件&quot;&gt;前提条件&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#初始化&quot; id=&quot;markdown-toc-初始化&quot;&gt;初始化&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#传播&quot; id=&quot;markdown-toc-传播&quot;&gt;传播&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#结束&quot; id=&quot;markdown-toc-结束&quot;&gt;结束&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#示例&quot; id=&quot;markdown-toc-示例&quot;&gt;示例&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#一致性切割-consistent-cut&quot; id=&quot;markdown-toc-一致性切割-consistent-cut&quot;&gt;一致性切割 Consistent Cut&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#flink的abs算法&quot; id=&quot;markdown-toc-flink的abs算法&quot;&gt;Flink的ABS算法&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#无环数据流&quot; id=&quot;markdown-toc-无环数据流&quot;&gt;无环数据流&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#有环数据流&quot; id=&quot;markdown-toc-有环数据流&quot;&gt;有环数据流&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#性能&quot; id=&quot;markdown-toc-性能&quot;&gt;性能&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考&quot; id=&quot;markdown-toc-参考&quot;&gt;参考&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;问题描述&quot;&gt;问题描述&lt;/h1&gt;

&lt;p&gt;考虑这样一个问题，一个分布式流处理系统，比如Flink，数据源源不断地从输入端涌入，系统中多个任务进程对数据进行各种计算，比如对某个数据进行求和，然后把处理后的数据结果发送给其他任务进程继续处理。系统中的任务进程是有状态的，比如数据求和的临时结果。如何对这样的系统保存全局快照，以应对系统崩溃等问题？&lt;/p&gt;

&lt;p&gt;直观的方法有两种，这两种方法都会”stop the world”。一种是让所有任务进程约定同一个时间点保存自身状态。然而我们知道&lt;a href=&quot;https://yang.observer/2020/07/11/time-ntp/&quot;&gt;系统时间无法做到完全同步&lt;/a&gt;，没法精确地让所有任务进程同时保存自身状态。另一种方法是让中心管理进程给所有任务进程发一个控制指令，所有任务进程停止处理新的数据，保存自己的状态。这种方式会中断整个系统的运行，显然不是我们想要的方案。而且这种方案会导致另外一个问题，那就是数据可能不一致。举个例子：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/not-consistent1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一个中心进程和两个任务进程P1, P2组成的分布式系统，P1和P2进程会相互发送应用层面的消息。中心进程在a时刻给两个任务进程分别发送了保存状态指令，P1进程先收到了指令，保存了P1自身状态，然后继续处理业务，给P2进程发送了一个应用消息。P2进程在d时刻先收到P1发来的应用消息，之后在d时刻终于收到了中心进程发来的保存状态指令，然后保存P2自身的状态。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/not-consistent2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这次的全局快照对应着大号黑线所标识的横截面。横截面左边的事件在快照中，右边的事件不属于。d节点属于全局快照中，c节点不属于全局状态。然而c节点发生在d节点之前，这违法了&lt;strong&gt;一致性&lt;/strong&gt;。也就是&lt;strong&gt;对于全局快照中保存的任何一个事件，在这个事件之前发生(happend before)的事件应该保存在这次的快照中&lt;/strong&gt;。&lt;/p&gt;

&lt;h1 id=&quot;chandy-lamport算法&quot;&gt;Chandy-Lamport算法&lt;/h1&gt;

&lt;p&gt;对于这个问题，早在1985年就由我们的老朋友Leslie Lamport和K. Mani Chandy研究过了。两位老爷子有一天吃着火锅唱着歌，宿醉了一个晚上后第二天想出了一个算法，也被称作&lt;a href=&quot;https://lamport.azurewebsites.net/pubs/chandy.pdf&quot;&gt;Chandy-Lamport算法&lt;/a&gt;。这个算法不会中断系统的正常运行，同时生成的快照符合一致性的要求。&lt;/p&gt;

&lt;h2 id=&quot;前提条件&quot;&gt;前提条件&lt;/h2&gt;

&lt;p&gt;首先描述算法中的一些定义：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;分布式系统由多个进程Pi组成。&lt;/li&gt;
  &lt;li&gt;每个进程通过网络通道和其他进程建立双向连接，Cij表示从Pi到Pj的连接通道，Cji表示从Pj到Pi的连接通道。&lt;/li&gt;
  &lt;li&gt;系统中的消息分为应用消息(application)和标记(marker)消息。应用消息是业务层面的消息，用M[xy]表示从x事件发到y事件的应用消息。标记消息是一种控制消息，用来生成快照。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个算法有一些前提条件：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;分布式系统中的进程不会崩溃。&lt;/li&gt;
  &lt;li&gt;进程之间的连接是保序的，也即FIFO Channel。&lt;/li&gt;
  &lt;li&gt;进程之间的消息是可靠传递的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;算法分为初始化、传播、结束三个步骤。&lt;/p&gt;

&lt;h2 id=&quot;初始化&quot;&gt;初始化&lt;/h2&gt;

&lt;p&gt;系统中任意一个进程Pi都可以发起快照请求。初始化的操作如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;1. Pi记录自己的状态
2. Pi发送一条marker消息给所有其他进程
3. Pi记录所有其他进程发来的application消息
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;传播&quot;&gt;传播&lt;/h2&gt;

&lt;p&gt;对于任意一个进程Pj，如果从Ckj通道上收到了从Pk进程发过来的消息，分两种情况处理。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;如果收到的消息是marker消息，并且第一次收到：
1. Pj记录自己的状态，把通道Ckj设置为空集合，不再记录Ckj上的消息。
2. 给所有其他进程发送marker消息，包括Pk。
3. 记录从其他通道Clj(l != k)上收到的application消息。

如果不是第一次收到marker消息：
把通道Ckj上记录的所有application消息作为通道Ckj的状态，不再记录之后发来的application消息。
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为什么要给其他所有进程发marker，包括Pk？&lt;/p&gt;

&lt;p&gt;给其他进程发送marker消息，目的就是告诉其他进程，在这条marker消息之后的application不用记录在这次的快照中。&lt;/p&gt;

&lt;h2 id=&quot;结束&quot;&gt;结束&lt;/h2&gt;

&lt;p&gt;当以下条件满足时，整个快照流程结束：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;1. 每个进程都收到了marker消息，并且记录了自己的状态。
2. 每个进程从每个通道中收到了marker消息，并且记录了通道的状态。
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;所有状态都记录完成后，可以由某个服务器收集这些分散的快照，形成全局快照。全局快照由每个进程的状态和每个通道的状态组成。这个收集过程就不详细讨论了。&lt;/p&gt;

&lt;h2 id=&quot;示例&quot;&gt;示例&lt;/h2&gt;

&lt;p&gt;举个例子来描述这个算法，这样我们会更加清楚算法的流程。这个例子来源于&lt;a href=&quot;https://courses.engr.illinois.edu/cs425/fa2017/&quot;&gt;Illinois University的Indranil Gupta教授的课程&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example1.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图中分布式系统由三个进程P1, P2, P3组成。黑色节点是application事件，包括进程内的事件和网络消息。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example2.png&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P1在b事件之后发起快照流程。P1首先记录自己的状态，这个状态包含a和b两个事件，然后给P2和P3发送marker消息，用图中红色虚线表示。之后开始记录从C21和C31通道发来的application消息。上图中用省略号表示正在记录这个通道的消息。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example3.png&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P3先收到marker消息，而且是第一次收到marker消息。P3记录自己的状态，包含i事件。然后设置通道C13为空集合。之后给P1和P2发送marker消息。最后开始记录C23通道发来的application消息。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example4.png&quot; alt=&quot;图六&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P1收到P3发来的marker消息，因为P1是这次快照流程的发起者，我们认为它已经收到过marker消息了，所以P1只需要把通道C31的状态设置为目前为止已经收到过的消息，也就是空集合。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example5.png&quot; alt=&quot;图七&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P2收到P3发来的marker消息，这是P2第一次收到marker消息。P2记录自己的状态，包含事件f, g, h。然后设置C32为空集合，之后给P1和P3发送marker消息，最后开始记录C12通道发来的application消息。&lt;/p&gt;

&lt;p&gt;现在所有进程都已经记录了自己的状态，但是算法还没结束，因为通道的状态还没记录完。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example6.png&quot; alt=&quot;图八&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P2终于收到了P1发来的marker消息，把C12设置为空集合。此时P2从每个通道都收到了marker消息，它记录了自己的状态，和每个通道的状态，P2的工作完成了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example7.png&quot; alt=&quot;图九&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P1从通道C21上先收到进程P2在h事件发来的application消息，把这一消息M[hd]加到C21中。然后收到了P2发来的marker消息，结束通道C21的状态记录，包含M[hd]消息。P1完成了自己工作。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example8.png&quot; alt=&quot;图十&quot; /&gt;&lt;/p&gt;

&lt;p&gt;进程P3从通道C23上收到了marker消息。它也完成了自己的工作。&lt;/p&gt;

&lt;p&gt;所有进程和所有通道的状态都记录完成，整个快照流程结束。&lt;/p&gt;

&lt;h1 id=&quot;一致性切割-consistent-cut&quot;&gt;一致性切割 Consistent Cut&lt;/h1&gt;

&lt;p&gt;上文提到过&lt;strong&gt;一致性：对于全局快照中保存的任何一个事件，在这个事件之前发生(happend before)的事件应该保存在这次的快照中&lt;/strong&gt;。这个也称因果一致性。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/example9.png&quot; alt=&quot;图十一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上个例子生成的快照是上图中大号黑线对应的横截面。黑线左边的事件都保存在快照中，属于过去发生的事件。黑线右边的事件都没有保存在快照中，属于未来发生的事件。Chandy-Lamport算法生成的快照满足一致性的要求，也叫做一致性切割(Consistent  Cut)。&lt;/p&gt;

&lt;p&gt;我们可以证明如果事件a happened before 事件b，b在快照中，那么a也在快照中：&lt;/p&gt;

&lt;p&gt;如果a和b属于同一个进程内的事件，那么命题显然是正确的。&lt;/p&gt;

&lt;p&gt;如果a是进程P的发送事件，b是进程Q的接收事件。因为b在快照中，那么进程Q肯定没有收到过marker消息，否则b事件不会在快照中。因为通道是可靠保序的，进程P肯定也没有发送过marker消息，所以a事件肯定也在快照中。&lt;/p&gt;

&lt;h1 id=&quot;flink的abs算法&quot;&gt;Flink的ABS算法&lt;/h1&gt;

&lt;p&gt;回到开篇的问题，Flink使用了一种称作&lt;a href=&quot;https://arxiv.org/pdf/1506.08603.pdf&quot;&gt;Asynchronous Barrier Snapshotting(ABS)算法&lt;/a&gt;来生成全局快照。ABS算法在Chandy-Lamport算法基础上做了一些改动，通过阶段性地保存每个算子(operator)的状态，可以做到不需要保存通道的状态，但还是要求算子之间的连接是保序可靠的，而且算法会局部地停止处理数据。根据数据流中是否有环，ABS的处理方式有区别。&lt;/p&gt;

&lt;h2 id=&quot;无环数据流&quot;&gt;无环数据流&lt;/h2&gt;

&lt;p&gt;算法的流程如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;中心协调者周期性地给所有输入源注入屏障barrier，也即Chandy-Lamport中的marker消息。输入源收到屏障barrier后，记录自己的状态，然后给所有的下游任务发送barrier。&lt;/li&gt;
  &lt;li&gt;中间任务节点从一个上游任务收到barrier，停止处理从这个上游任务发来的数据，其他上游任务的数据可以照常处理。&lt;/li&gt;
  &lt;li&gt;中间任务节点收到所有上游任务发来的barrier后，记录自己的状态，然后给所有下游任务发送barrier。&lt;/li&gt;
  &lt;li&gt;中间任务节点恢复处理所有上游任务发来的数据。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/abs1.png&quot; alt=&quot;图十二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是ABS算法的一个实例。系统中由两个输入源任务，两个中间统计任务，两个输出打印任务组成。&lt;/p&gt;

&lt;p&gt;图a)中两个输入源被中心协调者注入barrier，保存自身状态后分别给两个中间任务发送barrier。&lt;/p&gt;

&lt;p&gt;图b)中count-1任务收到两个输入源发来的barrier后，停止处理两个上游数据，记录自身状态，往print-1任务发送barrier，然后恢复两个上游数据的处理。count-2任务先收到src-1输入源发来的barrier，然后停止处理src-1发来的后续数据，等待src-2发来的barrier。&lt;/p&gt;

&lt;p&gt;图c)中count-2等到src-2的barrier也收到后，停止处理src-2发来的后续数据，记录自身状态，往print-2任务发送barrier，然后恢复两个上游数据的处理。&lt;/p&gt;

&lt;p&gt;图d)中print-1已经收到了count-1发来的barrier，完成了本阶段的处理，print-2的barrier还在路途中。等到print-2处理完barrier后，整个快照流程结束。&lt;/p&gt;

&lt;h2 id=&quot;有环数据流&quot;&gt;有环数据流&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/abs2.png&quot; alt=&quot;图十三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对于有环数据流，不能简单地照搬无环数据流的算法，因为这样会造成有环节点因为等待环路上的barrier而造成死锁。&lt;/p&gt;

&lt;p&gt;解决环的问题，首先需要标识出造成环路的那条边，称为back-edge，比如上图a)中连接中间两个任务的最上面的那条边。可以通过广度优先算法识别back-edge，如果一条边指向的终点已经遍历过了，这条边就是back-edge。&lt;/p&gt;

&lt;p&gt;识别出back-edge后，任务节点在等待上游发来的barrier时，把back-edge先排除在外。收到所有其他上游发来的barrier后，先记录自身状态，然后给所有下游发送barrier，此时，任务节点需要记录从back-edge发来的数据，直到从back-edge也收到了barrier消息。全局快照中除了每个任务节点的状态外，还包含所有从back-edge收到的数据。有环数据流的示例如上图所示。&lt;/p&gt;

&lt;h2 id=&quot;性能&quot;&gt;性能&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/pdf/1506.08603.pdf&quot;&gt;《Lightweight Asynchronous Snapshots for Distributed Dataflows》&lt;/a&gt; 论文中给出了ABS的性能测试结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-11-27-distributed-shapshots/abs3.png&quot; alt=&quot;图十四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;中间的图测试了ABS算法，同步算法(stop the world)和不执行任何快照算法这三种情况下的运行时开销。横坐标是快照间隔，纵坐标是运行时耗时。当快照间隔很小时，同步算法耗时比较多。这是因为同步会停止正常任务的执行，频繁快照时，这个耗时比较大。而ABS算法耗时要少很多。&lt;/p&gt;

&lt;p&gt;右边的图测试不同集群大小下，ABS算法的耗时。随着集群数量的增加，ABS算法的耗时并没有大幅增长。&lt;/p&gt;

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

&lt;p&gt;&lt;a href=&quot;https://lamport.azurewebsites.net/pubs/chandy.pdf&quot;&gt; Distributed Snapshots: Determining Global States of Distributed Systems&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://composition.al/blog/2019/04/26/an-example-run-of-the-chandy-lamport-snapshot-algorithm/&quot;&gt;An example run of the Chandy-Lamport snapshot algorithm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://courses.engr.illinois.edu/cs425/fa2017/&quot;&gt;Indranil Gupta的课程&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/pdf/1506.08603.pdf&quot;&gt;Lightweight Asynchronous Snapshots for Distributed Dataflows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.cs.princeton.edu/courses/archive/fall16/cos418/docs/P8-chandy-lamport.pdf&quot;&gt;P8-chandy-lamport&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 27 Nov 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/11/27/distributed-snapshots/</link>
        <guid isPermaLink="true">https://yang.observer/2021/11/27/distributed-snapshots/</guid>
        
        <category>Distributed Snapshots</category>
        
        <category>Global States</category>
        
        <category>Chandy-Lamport</category>
        
        <category>全局状态</category>
        
        <category>分布式系统</category>
        
        <category>快照</category>
        
        
      </item>
    
      <item>
        <title>Stars</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#northern-milky-way&quot; id=&quot;markdown-toc-northern-milky-way&quot;&gt;Northern Milky Way&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#southern-milky-way&quot; id=&quot;markdown-toc-southern-milky-way&quot;&gt;Southern Milky Way&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#magellanic-cloud&quot; id=&quot;markdown-toc-magellanic-cloud&quot;&gt;Magellanic Cloud&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#southern-cross&quot; id=&quot;markdown-toc-southern-cross&quot;&gt;Southern Cross&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#orion&quot; id=&quot;markdown-toc-orion&quot;&gt;Orion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#radio-telescope&quot; id=&quot;markdown-toc-radio-telescope&quot;&gt;Radio telescope&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#star-trails&quot; id=&quot;markdown-toc-star-trails&quot;&gt;Star trails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All photos are taken by &lt;strong&gt;Sony RX100&lt;/strong&gt;.&lt;/p&gt;

&lt;h1 id=&quot;northern-milky-way&quot;&gt;Northern Milky Way&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-northern-milky-way.jpg&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;southern-milky-way&quot;&gt;Southern Milky Way&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-southern-milky-way.jpg&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-southern-milky-way-part.jpg&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;magellanic-cloud&quot;&gt;Magellanic Cloud&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-magellanic-cloud.jpg&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;southern-cross&quot;&gt;Southern Cross&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-southern-cross.jpg&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;orion&quot;&gt;Orion&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-orion.jpg&quot; alt=&quot;图六&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;radio-telescope&quot;&gt;Radio telescope&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-radio-telescope.jpg&quot; alt=&quot;图七&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;star-trails&quot;&gt;Star trails&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-08-21-star/post-star-trails.jpg&quot; alt=&quot;图七&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 21 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/08/21/star/</link>
        <guid isPermaLink="true">https://yang.observer/2021/08/21/star/</guid>
        
        <category>Milky way</category>
        
        <category>Orion</category>
        
        <category>Southern Cross</category>
        
        <category>Magellanic Cloud</category>
        
        <category>radio telescope</category>
        
        
      </item>
    
      <item>
        <title>期权投资策略（八）：put</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#买入看跌期权&quot; id=&quot;markdown-toc-买入看跌期权&quot;&gt;买入看跌期权&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#持有股票同时买入看跌期权&quot; id=&quot;markdown-toc-持有股票同时买入看跌期权&quot;&gt;持有股票同时买入看跌期权&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#买入看涨期权同时买入看跌期权&quot; id=&quot;markdown-toc-买入看涨期权同时买入看跌期权&quot;&gt;买入看涨期权同时买入看跌期权&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#买入跨式套利&quot; id=&quot;markdown-toc-买入跨式套利&quot;&gt;买入跨式套利&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#买入宽跨式套利&quot; id=&quot;markdown-toc-买入宽跨式套利&quot;&gt;买入宽跨式套利&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#卖出看跌期权&quot; id=&quot;markdown-toc-卖出看跌期权&quot;&gt;卖出看跌期权&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#未持保跨式套利立权&quot; id=&quot;markdown-toc-未持保跨式套利立权&quot;&gt;未持保跨式套利立权&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍看跌期权(put)相关的内容。&lt;/p&gt;

&lt;p&gt;（本系列文章的目的是加强自己的学习效果，内容主要来源于期权领域的经典&lt;a href=&quot;https://book.douban.com/subject/26345971/&quot;&gt;《期权投资策略》&lt;/a&gt;和自己的理解）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;风险提示：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;买入看跌期权&quot;&gt;买入看跌期权&lt;/h1&gt;

&lt;p&gt;相比实值看跌期权，虚值看跌期权的潜在回报更高，风险也更大。如果股票价格大跌，虚值看跌期权的百分比回报更大。不过，如果股票价格下跌幅度不大，实值期权一般是更好的选择。看跌期权在变为实值后的时间价值权利金失去的更为迅速。&lt;/p&gt;

&lt;p&gt;举个例子，&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股价是49
XYZ 7月45 put价格是1
XYZ 7月50 put价格是3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果XYZ下跌了5点，变成44。7月45 put的价格可能只增加了2点，变成3。而7月50 put的价格可能是7。一旦put变成实值，即使是长期put，时间价值权利金也会减少。所以如果长期put的价格和短期相比差距不大，应该选择长期put。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;看跌期权delta = 看涨期权delta - 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;深度虚值put的delta接近0。&lt;/p&gt;

&lt;h1 id=&quot;持有股票同时买入看跌期权&quot;&gt;持有股票同时买入看跌期权&lt;/h1&gt;

&lt;p&gt;在持有股票的同时买入put，这样的头寸也叫合成买入call，因为盈利图形和买入call的盈利图形一样，整个头寸下跌风险有限，潜在收益无限。举个例子，XYZ股票买入价格为52，同时买入一张10月50 put，价格为2。整个头寸的最大亏损是4点，盈利图形如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-05-23-option-strategy-put/post-put1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;虚值put的成本很小，但提供的保护不如实值put。一般来说，应该买入略微虚值的put作为保护，这样可以在保护作用和限制盈利之间达到一种平衡。&lt;/p&gt;

&lt;h1 id=&quot;买入看涨期权同时买入看跌期权&quot;&gt;买入看涨期权同时买入看跌期权&lt;/h1&gt;

&lt;h2 id=&quot;买入跨式套利&quot;&gt;买入跨式套利&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;买入跨式套利(straddle buying)&lt;/strong&gt;策略指买入相同行权价和到期日的call和put。无论股票朝哪个方向运动，只有运动得足够远，就有大量的潜在盈利，最大亏损就是买call和put投入的钱。&lt;/p&gt;

&lt;p&gt;举个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股价50
XYZ 7月50 call价格3
XYZ 7月50 put价格2
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同时买入XYZ 7月50 call和XYZ 7月50 put，整个跨式套利成本是5点。如果XYZ到期时股价是50，整个投资全部亏损。如果XYZ价格到期时股价超过55或者低于45，call或者put的价格就会超过5点，头寸就会有盈利。盈利图形如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-05-23-option-strategy-put/post-put2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;跨式套利可以用于赌财报，财报发布当日，股价常常暴涨暴跌，如果提前买入跨式套利，就有可能盈利。&lt;/p&gt;

&lt;h2 id=&quot;买入宽跨式套利&quot;&gt;买入宽跨式套利&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://yang.observer/2021/02/15/option-strategy-call-buying/&quot;&gt;前文&lt;/a&gt;介绍过，对于看涨期权买入，如果股票上涨，有4种善后方法：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;平仓提取盈利&lt;/li&gt;
  &lt;li&gt;什么也不做&lt;/li&gt;
  &lt;li&gt;向上移仓&lt;/li&gt;
  &lt;li&gt;卖出虚值看涨期权&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;还有另外一种方式就是&lt;strong&gt;买入put&lt;/strong&gt;。如果股价继续上涨，call还有继续盈利的可能。&lt;/p&gt;

&lt;p&gt;举个例子，XYZ股价48，投资者买入一张XYZ 10月50 call价格3点。股价涨跌58，call的价格涨到9。此时投资者买入10月60 put价格4点，整个头寸是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ 10月50 call价格3
XYZ 10月60 put价格4
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个头寸也叫&lt;strong&gt;宽跨式套利(strangle)&lt;/strong&gt;，它的价格至少是10点。如果XYZ到期时价格在50和60直接，头寸的价格是10，如果在这个范围之外，头寸的价格会更高。&lt;/p&gt;

&lt;p&gt;宽跨式套利由到期日相同，行权价不同的call和put组成。举个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股价47
XYZ 1月45 put价格2
XYZ 1月50 call价格2
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;买入一张XYZ 1月45 put和一张XYZ 1月50 call，两个期权都是虚值的，如果到期时XYZ在45和50之间，两个期权都过期，投资全部亏损。如果XYZ高于54或者低于41，头寸开始盈利。和跨式套利相比，宽跨式套利出现最大亏损的价格范围更大一些，跨式套利之一在股票价格刚好等于行权价时亏损最大。但宽跨式套利的亏损额要小一些，这是一个弥补的因素。盈利曲线如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-05-23-option-strategy-put/post-put3.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;宽跨式套利也可以用实值期权，但是总成本更高，风险更低，相对虚值期权，实值期权更保守。&lt;/p&gt;

&lt;h1 id=&quot;卖出看跌期权&quot;&gt;卖出看跌期权&lt;/h1&gt;

&lt;p&gt;未持保看跌期权（uncovered put sale），也叫裸put，指直接出售put。这是一个看多的策略。策略出售者获得了期权权利金，同时有义务按照行权价买入股票。举个例子，XYZ股价是50，6月50 put价格是4点，如果到期时股价在50以上，获得最大盈利400，如果到期时股价下跌，则可能会大亏。盈利曲线如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-05-23-option-strategy-put/post-put4.png&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;未持保看跌期权立权的图形和持保看着期权立权(covered call sale)的图形一致，两种策略是等价的。&lt;/p&gt;

&lt;p&gt;未持保看跌期权立权的一种常见用法是低于市价买入股票。比如XYZ当前价格是60，投资者觉得贵了，想55买入。这时如果3个月的put价格是5，投资者可以卖出put，获得5点权利金。如果到期时XYZ股价在60以下，投资者就可以以60的价格买入股票，再减去权利金5点，买入XYZ的成本实际是55。如果到期时XYZ股价在60以上，期权作废，获得了5点权利金，没有机会按55买入股票。如果是自己非常想买入的股票，慎用这个策略，有可能没机会买入。&lt;/p&gt;

&lt;h1 id=&quot;未持保跨式套利立权&quot;&gt;未持保跨式套利立权&lt;/h1&gt;

&lt;p&gt;上面介绍了买入跨式套利(straddle buyging)，投资者也可以卖出跨式套利，也叫做未持保跨式套利立权(uncovered straddle write)，该策略卖出call和put，是潜在盈利有限而潜在风险巨大的中性策略，不过它获得盈利的概率相当大，如果股价变化不大就可能盈利。&lt;/p&gt;

&lt;p&gt;举个例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股价45
XYZ 1月45 call价格4
XYZ 1月45 put价格3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;跨式套利可以卖出7点，如果股价在到期时高于38并且低于52，卖出该跨式套利就会盈利。盈利图形如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-05-23-option-strategy-put/post-put5.png&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;p&gt;未持保跨式套利立权如何善后？&lt;strong&gt;本质上说，善后行动应该做两件事情：首先限制头寸中的风险。其次保留潜在盈利的余地&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;还是上面这个例子，股价是45时，以7点卖出跨式套利，之后股价上涨，价格如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股价50
XYZ 1月45 call价格7
XYZ 1月50 put价格1
XYZ 1月50 call价格3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时这个跨式套利中有很大的时间价值权利金，买回来会相当贵。此时买入1月50 call作为上行方向的保护，头寸变成了总信用是4点的头寸。如果到期时XYZ价格高于50，put作废，投资者需要付出5点将头寸平仓（买入1月45 call，卖出1月50 call），此时最大亏损就是1点。&lt;/p&gt;

&lt;p&gt;除了能限制上行方向的亏损，这类善后行动给潜在盈利留有余地。如果到期时XYZ股价在41和49之间，投资者可以用小于4点的价格将头寸平仓，获得盈利。只有股价跌到40以下才有大笔的亏损。&lt;/p&gt;
</description>
        <pubDate>Sun, 23 May 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/05/23/option-strategy-put/</link>
        <guid isPermaLink="true">https://yang.observer/2021/05/23/option-strategy-put/</guid>
        
        <category>option investment strategy</category>
        
        <category>put</category>
        
        <category>straddle</category>
        
        <category>strangle</category>
        
        <category>看跌期权</category>
        
        
      </item>
    
      <item>
        <title>期权投资策略（七）：volatility</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#什么是波动率&quot; id=&quot;markdown-toc-什么是波动率&quot;&gt;什么是波动率&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#vega&quot; id=&quot;markdown-toc-vega&quot;&gt;vega&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gamma&quot; id=&quot;markdown-toc-gamma&quot;&gt;gamma&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#历史波动率和隐含波动率的关系&quot; id=&quot;markdown-toc-历史波动率和隐含波动率的关系&quot;&gt;历史波动率和隐含波动率的关系&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#被误解的时间价值&quot; id=&quot;markdown-toc-被误解的时间价值&quot;&gt;被误解的时间价值&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#交易波动率&quot; id=&quot;markdown-toc-交易波动率&quot;&gt;交易波动率&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍波动率(volatility)相关的内容。&lt;/p&gt;

&lt;p&gt;（本系列文章的目的是加强自己的学习效果，内容主要来源于期权领域的经典&lt;a href=&quot;https://book.douban.com/subject/26345971/&quot;&gt;《期权投资策略》&lt;/a&gt;和自己的理解）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;风险提示：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;什么是波动率&quot;&gt;什么是波动率&lt;/h1&gt;

&lt;p&gt;前文多次提到过波动率，波动率高的时候，期权价格也跟着上升，那究竟什么是波动率，波动率是怎么算出来的？&lt;/p&gt;

&lt;p&gt;波动率(Volatility)有两种，一种是历史波动率(HV, History Volatility)，另一种是隐含波动率(IV, Implied Volatility)。&lt;/p&gt;

&lt;p&gt;历史波动率的定义是股票价格的年化标准差。数学公式是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-04-14-option-strategy-volatility/post-volatility1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;根据正态分布，我们知道数值在1个标准差内的概率是68%，2个标准差内的概率是95%，3个标准差内的概率是99.7%。如果股票XYZ的历史波动率是32%，那么一年内XYZ的股价有68%的概率在32%的涨跌范围之内。&lt;/p&gt;

&lt;p&gt;隐含波动率的意思就是市场自身隐含的波动率，它基于有效市场假说，认为行权价接近当前股价的期权和交易量比较大的期权的市场定价是合理的，然后把当前期权价格代入期权定价公式，计算出波动率，这个波动率就叫隐含波动率。前文我们介绍过期权的定价公式，通过股票价格、期权行权价、期权有效期、股票波动率、无风险利率来算出期权价格，现在期权价格已知，就可以反推出隐含波动率的值。&lt;/p&gt;

&lt;h1 id=&quot;vega&quot;&gt;vega&lt;/h1&gt;

&lt;p&gt;前文提到过两个希腊字母delta和theta，delta表示股价变动1点时对应的期权价格的变化值，theta表示期权价格随时间每天减少的值。波动率对期权价格造成的影响也有一个希腊字母表示，vega表示&lt;strong&gt;波动率每变化一个百分点造成的期权价格变化值&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;举个例子，XYZ股价是50，XYZ 7月50 call的价格是7.25，计算出当前的IV是70%。如果IV变成了71%，根据期权定价模型计算出的XYZ 7月50 call的价格是7.35，也就是vega的值是 0.10。&lt;/p&gt;

&lt;p&gt;vega有如下几个特点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;相同行权价和到期日的call和put的vega值相同。&lt;/li&gt;
  &lt;li&gt;平值期权的vega最大，随着实值和虚值的程度，vega值变小。&lt;/li&gt;
  &lt;li&gt;vega值随剩余时间的减少而减少，长期期权更容易受到波动率的影响。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;gamma&quot;&gt;gamma&lt;/h1&gt;

&lt;p&gt;gamma指&lt;strong&gt;股票价格变化1点时，delta变化的值&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;举个例子，XYZ股价是49，1月50 call的delta是0.5，gamma是0.05。如果XYZ的股价上涨1点到50，call的delta增加0.05，变成0.55。&lt;/p&gt;

&lt;p&gt;gamma有如下几个特点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;当股票价格接近行权价时，gamma的值最大，当股票价格离开行权价，不管朝哪个方向，gamma的值都减小。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;深度实值或深度虚值的期权的gamma接近于0。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;所有期权的gamma都是正数，不管是call还是put，卖出期权gamma是负数。&lt;/li&gt;
  &lt;li&gt;平值短期期权的gamma高于长期期权的gamma。&lt;/li&gt;
  &lt;li&gt;平值低波动率期权的gamma高于高波动率期权的gamma。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;历史波动率和隐含波动率的关系&quot;&gt;历史波动率和隐含波动率的关系&lt;/h1&gt;

&lt;p&gt;隐含波动率是市场对未来的预期，因为计算的时候是以当前期权价格倒推出的，而期权的当前价格体现了市场对股票未来的预期，如果市场预期股票未来会大涨，就很有可能愿意出较高的价格买入期权，从而导致隐含波动率升高。但是这个&lt;strong&gt;预期可能是不准确的&lt;/strong&gt;，导致HV和IV之间的差别较大。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-04-14-option-strategy-volatility/post-volatility2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是某个股票的历史波动率和隐含波动率的对比图，可以看到&lt;strong&gt;隐含波动率绝大部分情况都比历史波动率要高&lt;/strong&gt;。对于其他股票来说，也同样有这个规律，市场大部分情况会高估股票的波动。如果隐含波动率远高于历史波动率，买入期权就划不来，因为很可能隐含波动率会下降，这时期权的价格也会降低。对于卖出期权的人，这个时候就很划得来。因为波动率有这种均值回归的趋势，这也是波动率交易的盈利方式，在低波动率的时候做多波动率，在高波动率的时候做空波动率。&lt;/p&gt;

&lt;p&gt;隐含波动率突然变大的原因有两个。一个是内幕消息，有人提前获知了股票的某个消息，认为股票会有巨大波动，这些人通过购买期权的方式提前埋伏。另外一个是有人想要套利，比如建仓covered call。&lt;/p&gt;

&lt;h1 id=&quot;被误解的时间价值&quot;&gt;被误解的时间价值&lt;/h1&gt;

&lt;p&gt;我们介绍过期权的价格由内涵价值和时间价值两部分组成。时间价值这个说法不太准确，我们可以把除内涵价值之外的部分叫做&lt;strong&gt;多余价值&lt;/strong&gt;。影响多余价值的因素其实有5个。包括：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;股票价格的变化&lt;/li&gt;
  &lt;li&gt;隐含波动率的变化&lt;/li&gt;
  &lt;li&gt;时间的消逝&lt;/li&gt;
  &lt;li&gt;股息的变化&lt;/li&gt;
  &lt;li&gt;利率的变化&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这5个因素，除了股息的变化外，其他4个每个都有一个希腊字母字母来表示：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;股票价格的变化： delta&lt;/li&gt;
  &lt;li&gt;隐含波动率的变化： vega&lt;/li&gt;
  &lt;li&gt;时间的消逝：theta&lt;/li&gt;
  &lt;li&gt;利率的变化：rho&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中股息和利率的变化对期权价格的影响相对较小。如果所有条件都不变，时间就会慢慢消磨掉期权的多余价值。但是如果以天为单位计算，一两天内时间对期权价值的影响可能没有delta和vega的影响大。举个例子：当前XYZ的股价是82，XYZ 1月80 call的价格是8，其中内涵价值是2，多余价值是6。如果某天期权的隐含波动率在50%之上，几个希腊字母的值可能如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;delta   0.60
vege    0.13
theta  -0.06
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也就是说股价的变化造成的影响最大，隐含波动率造成的影响有0.13，而时间消耗每天只有0.06。从这点看，&lt;strong&gt;期权交易者的敌人不单是时间，波动率也可能是另外一个敌人&lt;/strong&gt;。如果隐含波动率高的时候买入期权，即使之后股价的方向猜对了，但是隐含波动率的下降可能会抵消掉股价的运动。&lt;/p&gt;

&lt;h1 id=&quot;交易波动率&quot;&gt;交易波动率&lt;/h1&gt;

&lt;p&gt;波动率具有均值回归的性质，这点和股票不一样，对于好公司而言，股票有可能长期上涨，但是波动率始终在一个范围内波动，不可能一直向上涨，这就为交易波动率提供了条件。&lt;/p&gt;

&lt;p&gt;波动率交易有两种基本方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;比较当前IV和历史IV&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;比较IV和HV&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第一种方法是比较当前IV处于历史IV的百分位，判断当前IV是高还是低。比如当前IV处于90百分位，那就属于高位，很有可能会降低，这时就可以做空波动率。不过要注意波动率高低的绝对值，如果100百分位是45%，1百分位是40%，那就没有什么交易的空间。&lt;/p&gt;

&lt;p&gt;第二种方法是比较当前IV和当前HV。因为IV和HV有靠拢的趋势，如果在两者差距很大的时候建仓，比如IV在60%，HV在40%的时候做空波动率。但是这种方法可能有问题。一是不知道这两者是否一定能靠拢或者多长时间后才能靠拢，二是即使靠拢也可能是两者都上涨或者下跌，比如两者都跌到30%。&lt;/p&gt;
</description>
        <pubDate>Wed, 14 Apr 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/04/14/option-strategy-volatility/</link>
        <guid isPermaLink="true">https://yang.observer/2021/04/14/option-strategy-volatility/</guid>
        
        <category>option investment strategy</category>
        
        <category>volatility</category>
        
        <category>波动率</category>
        
        
      </item>
    
      <item>
        <title>期权投资策略（六）：LEAPS</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#什么是leaps&quot; id=&quot;markdown-toc-什么是leaps&quot;&gt;什么是LEAPS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#长期期权定价&quot; id=&quot;markdown-toc-长期期权定价&quot;&gt;长期期权定价&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#长期期权策略&quot; id=&quot;markdown-toc-长期期权策略&quot;&gt;长期期权策略&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#取代股票&quot; id=&quot;markdown-toc-取代股票&quot;&gt;取代股票&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#投机&quot; id=&quot;markdown-toc-投机&quot;&gt;投机&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍长期期权(LEAPS)相关的内容。&lt;/p&gt;

&lt;p&gt;（本系列文章的目的是加强自己的学习效果，内容主要来源于期权领域的经典&lt;a href=&quot;https://book.douban.com/subject/26345971/&quot;&gt;《期权投资策略》&lt;/a&gt;和自己的理解）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;风险提示：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;什么是leaps&quot;&gt;什么是LEAPS&lt;/h1&gt;

&lt;p&gt;LEAPS的全称是Long Term Equity Anticipation Securities，中文叫长期期权。从名字上可以看出，它是一种到期日比较长的期权，可以是call，也可以是put。至于到期日多长叫长期期权，有说9个月以上的，有说1年以上的，还有说2年以上的。我们就认为1年以上的是长期期权。长期期权大多数特点和中短期期权差不多，本文主要介绍区别之处。&lt;/p&gt;

&lt;h1 id=&quot;长期期权定价&quot;&gt;长期期权定价&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-03-03-option-strategy-leaps/post-leaps1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是长期期权的价格曲线，和中短期相比，长期期权的曲线比较平坦，时间价值要多不少。&lt;/p&gt;

&lt;p&gt;前文&lt;a href=&quot;http://yang.observer/2021/02/13/option-strategy-introduction/&quot;&gt;期权投资策略（一）：期权介绍&lt;/a&gt;中介绍过，影响期权价格的主要因素有四个：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;股票价格&lt;/li&gt;
  &lt;li&gt;期权行权价&lt;/li&gt;
  &lt;li&gt;期权剩余时间&lt;/li&gt;
  &lt;li&gt;股票波动率&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个说法并不准确，其实有6个因素，还有2个是&lt;strong&gt;无风险利率和股息率&lt;/strong&gt;。无风险利率可以认为是美国国债的利率。对于中短期期权，无风险利率和股息率的影响不大，但对于长期期权影响比较大。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-03-03-option-strategy-leaps/post-leaps2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是无风险利率分别是3%，6%，9%时，行权价为100的长期期权价格。股价是110时，3%利率下的期权价格是20左右，而9%利率下的价格是30左右，差别巨大。另外从图中可以发现从左到右，3条曲线之间的间距越来越大，也就是说&lt;strong&gt;实值长期期权比虚值长期期权受利率的影响更大&lt;/strong&gt;。最近美国国债的利率上升，长期期权的价格也会上升。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-03-03-option-strategy-leaps/post-leaps3.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是股息率对长期期权的影响。3条曲线，下面的曲线比上面的曲线股息要多1刀。也就是说，&lt;strong&gt;股息率越大，长期期权的价格越低&lt;/strong&gt;。原因是因为股息率越高，除息时股价越低，而期权不会得到股息。&lt;/p&gt;

&lt;p&gt;比较上面两个图，&lt;strong&gt;和股息率相比，无风险利率对长期期权的影响要大一些&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-03-03-option-strategy-leaps/post-leaps4.png&quot; alt=&quot;图四&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上表从股价、波动率、利率、股息四个方面比较短期期权和长期期权的区别。需要注意的是，&lt;strong&gt;波动率对长期期权的影响比短期期权大很多&lt;/strong&gt;。所以一般是在&lt;strong&gt;低利率和低隐含波动率的时候买入长期期权&lt;/strong&gt;，而不是反向操作。&lt;/p&gt;

&lt;h1 id=&quot;长期期权策略&quot;&gt;长期期权策略&lt;/h1&gt;

&lt;h2 id=&quot;取代股票&quot;&gt;取代股票&lt;/h2&gt;

&lt;p&gt;长期期权的一种玩法是用来取代股票。比如说我们想买入股票XYZ，现在XYZ也提供1年后到期的长期期权。那么买入正股和长期期权哪个划算呢？我们来计算一下。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股票价格50
1年长期期权行权价40的call价格12
XYZ股息是每股0.5
无风险利率5%
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比较买入100股XYZ和1张40 call：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;股票成本 5000
期权成本 1200
------------
期权节省 3800
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果拿这3800存入存入银行或者买债券，假设1年后股价没变化还是50，使用长期期权的方式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;利息收入 3800 * 0.05 = 190
股息损失 100 * 0.5 = 50
时间价值权利金损失 100 * 2 = 200
净机会成本 -60
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;和买入正股相比，买入长期期权的成本要多60。再分析1年后到期时不同股价下两种方式的区别：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果到期时股价高于50，两者的收益是相同的，也就是100股正股能赚多少，1张长期期权也能赚相同的数额，但是长期期权的成本比正股多了60。&lt;/li&gt;
  &lt;li&gt;如果到期时股价低于50，两者都会亏损，如果股价低于40，正股会遭受大量亏损，但是长期期权的下行风险最大也就是股价是40的时候，即使价格再低，也只是期权作废。而且长期期权有3800存在银行，同时有190的利息，最大的亏损是1010。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;买入实值长期期权，同时将省下的相同成本的股票的差额存入银行是一个划算的策略&lt;/strong&gt;。注意这个策略里面需要把省下的钱存入银行，而不是全部买入长期期权。&lt;/p&gt;

&lt;h2 id=&quot;投机&quot;&gt;投机&lt;/h2&gt;

&lt;p&gt;和上面的策略相比，投机的意思就是把本金全部用来买入长期期权，而不是把省下的钱存入银行。和&lt;a href=&quot;http://yang.observer/2021/02/15/option-strategy-call-buying/&quot;&gt;期权投资策略（二）：call buying&lt;/a&gt;中介绍的买入普通期权相比，大部分特性相同，主要的区别在于时间价值的递减速度和delta的变化上。&lt;/p&gt;

&lt;p&gt;长期期权由于到期时间比较长，时间价值的减少也比较慢。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-03-03-option-strategy-leaps/post-leaps5.png&quot; alt=&quot;图五&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图描绘了一个平值长期期权和虚值长期期权的时间价值减少曲线。2年的平值长期期权，剩余时间还有6个月的时候，时间价值开始加速递减，所以我们可以在&lt;strong&gt;期权还剩6个月前，移仓到远期的期权&lt;/strong&gt;。另外虚值的长期期权时间价值递减比平值的要快。&lt;/p&gt;

&lt;p&gt;长期期权delta的变化和中短期期权相比也不一样。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-03-03-option-strategy-leaps/post-leaps6.png&quot; alt=&quot;图六&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图是两个行权价都是100的2年长期期权和3个月短期期权的delta随着股价变化的曲线。可以看出，2年长期期权的delta变化相对比较平缓。3个月短期期权的delta在期权虚值的时候非常低，而在期权实值的时候非常高。&lt;/p&gt;

</description>
        <pubDate>Wed, 03 Mar 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/03/03/option-strategy-leaps/</link>
        <guid isPermaLink="true">https://yang.observer/2021/03/03/option-strategy-leaps/</guid>
        
        <category>option investment strategy</category>
        
        <category>LEAPS</category>
        
        <category>长期期权</category>
        
        
      </item>
    
      <item>
        <title>期权投资策略（五）：calendar spread</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#什么是套利&quot; id=&quot;markdown-toc-什么是套利&quot;&gt;什么是套利&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#策略介绍&quot; id=&quot;markdown-toc-策略介绍&quot;&gt;策略介绍&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#中性日历套利&quot; id=&quot;markdown-toc-中性日历套利&quot;&gt;中性日历套利&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#看多日历套利&quot; id=&quot;markdown-toc-看多日历套利&quot;&gt;看多日历套利&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#善后行动&quot; id=&quot;markdown-toc-善后行动&quot;&gt;善后行动&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#中性日历套利善后&quot; id=&quot;markdown-toc-中性日历套利善后&quot;&gt;中性日历套利善后&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#看多日历套利善后&quot; id=&quot;markdown-toc-看多日历套利善后&quot;&gt;看多日历套利善后&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍日历套利(calendar spread)相关的内容。&lt;/p&gt;

&lt;p&gt;（本系列文章的目的是加强自己的学习效果，内容主要来源于期权领域的经典&lt;a href=&quot;https://book.douban.com/subject/26345971/&quot;&gt;《期权投资策略》&lt;/a&gt;和自己的理解）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;风险提示：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;什么是套利&quot;&gt;什么是套利&lt;/h1&gt;

&lt;p&gt;套利是指&lt;strong&gt;买入期权，同时卖出不同行权价或者到期日的另一个期权&lt;/strong&gt;。通过同时买进和卖出期权，可以减少单个操作的风险。比如卖出期权可能是一个风险无限的头寸，但同时买进另一个期权就可以显著降低风险。套利可以使用看涨期权(call)来建立，也可以使用看跌期权(put)。套利分为三类：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;垂直套利(vertical spread)：买入和卖出到期日相同但行权价不同的期权。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;水平套利(horizontal spread)：买入和卖出行权价相同但到期日不同的期权。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;对角套利(diagonal spread)：买入和卖出行权价和到期日都不相同的期权。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;之所以有这三种分类，看下面这张图应该就能明白原因。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-02-26-option-strategy-calendar-spread/post-calendar-spread1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果建立套利头寸时的总价格产生现金流入，这个套利也称为信用(credit)套利。比如买入期权花了1点，卖出期权得到2点，这个套利就产生了1点信用。如果建立套利头寸时的总价格产生现金流出，这个套利称为债务(debit)套利。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权策略的基本哲学是卖出时间价值，买进内涵价值&lt;/strong&gt;。&lt;/p&gt;

&lt;h1 id=&quot;策略介绍&quot;&gt;策略介绍&lt;/h1&gt;

&lt;p&gt;日历套利(calendar spread)也叫时间套利(time spread)，是一种水平套利，形式是&lt;strong&gt;卖出1张call，同时买入1张更远期call，两张call的行权价相同&lt;/strong&gt;。这个策略的原理是近期期权的时间价值比远期期权的时间价值减少地更快，这样就可以产生盈利。日历套利可以是中性的，也可以是看多的。&lt;/p&gt;

&lt;h2 id=&quot;中性日历套利&quot;&gt;中性日历套利&lt;/h2&gt;

&lt;p&gt;建仓时如果采用平值期权，也就是期权行权价等于或者接近股票价格，就是中性的。举个例子，当前XYZ有三种期权，价格如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ 股票价格50
XYZ 4月50 call，价格5
XYZ 7月50 call，价格8
XYZ 10月50 call，价格10
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果sell 4月50 call，buy 7月50 call，需要付出3点债务(depit)。4月到期时，假设股票价格没有变化，期权的价格如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ 4月50 call，价格0
XYZ 7月50 call，价格5
XYZ 10月50 call，价格8
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时4月50 call作废，7月50 call值5点，整个组合赚了2点。整个组合的最大亏损不会超过3点，也就是最初建立头寸时的债务，因为到期时不管股票价格是多少，这两个期权之间的价格差距不会超过5点。可以进一步分析：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果4月到期时股票价格低于50，比如是40，此时4月50 call作废，7月50 call的价格可能是2，此时平仓，整个组合亏损1点。如果股票价格更低，7月50 call的价格也接近于0，整个组合最多亏3点。&lt;/li&gt;
  &lt;li&gt;如果4月到期时股票价格高于50，比如时60，此时4月50 call的价格是10，7月50 call的价格可能是12（内涵价值是10，时间价值可能是2，因为时间价值在减少，所以肯定小于5），此时期权的价差是2点，如果平仓，整个组合亏损1点。如果股票价格更高，这两个期权之间的价差会更小，最小接近于0，整个组合最多亏3点。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;中性日历套利的盈利曲线如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-02-26-option-strategy-calendar-spread/post-calendar-spread2.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;整个组合的最大盈利跟到期时的&lt;strong&gt;股票波动性&lt;/strong&gt;有关，股票的波动性不一样，导致期权的价格不一样。比如到期前股票经常上蹿下跳50%，到期日股价还是50，但这时7月50 call的价格可能很高，导致盈利也很高。盈利公式：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;最大盈利 = 初始近期看涨期权价格 - 初始远期看涨期权价格 + 到期时远期看涨期权价格 - 到期时近期看涨期权价格
最大亏损 = 初始近期看涨期权价格 - 初始远期看涨期权价格
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最好是建仓时股票的波动性比较低，到期时波动性较高，这样盈利会比较大。如果是相反，建仓时波动性比较高，盈利就比较低，甚至有波动性降低造成的亏损大于时间价值减少带来的盈利，导致整体亏损。&lt;/p&gt;

&lt;h2 id=&quot;看多日历套利&quot;&gt;看多日历套利&lt;/h2&gt;

&lt;p&gt;如果看多股票，可以使用虚值call来建仓日历套利。举个例子，当前XYZ价格如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ 股票价格45
XYZ 4月50 call价格1
XYZ 7月50 call价格1.5
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果sell 4月50 call，buy 7月50 call，这就是一个看多的日历套利。到期时我们希望XYZ能低于50，这样4月的call能作废，之后我们希望XYZ能上涨，这样7月的call就能大赚。4月call作废的概率比较大，前文介绍过，虚值期权的delta小于0.5，也就是说股票达到行权价的概率比较低。同理7月call到期时，股票达到行权价的概率也比较低。但是一旦达到，盈利就会比较大。所以建仓看多日历套利有下面几个准则：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;选择波动性比较大的股票，这样股票有机会运动到远期行权价之上。&lt;/li&gt;
  &lt;li&gt;不要使用深度虚值的期权，这样股票达到远期行权价的概率很低。&lt;/li&gt;
  &lt;li&gt;不要在看多日历套利上投入很大的仓位。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;善后行动&quot;&gt;善后行动&lt;/h1&gt;

&lt;h2 id=&quot;中性日历套利善后&quot;&gt;中性日历套利善后&lt;/h2&gt;

&lt;p&gt;中性日历套利的善后分三种情况：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果股票没有太大变化，这样近期期权到期时可能作废，此时达到最大盈利，可以将远期期权平仓。也可以只平仓部分远期期权，收回成本，剩下的远期期权留着，如果之后股票上涨，盈利就会比较大。&lt;/li&gt;
  &lt;li&gt;如果股票下跌，此时有两种选择，一是立即平仓止损，此时亏损比较少。另一种是不去管它，让近期的期权作废，然后等待股票上涨的机会，这样远期期权还有盈利的机会。股票下跌时不可取的做法是把买入的远期期权卖掉，留着卖出的近期期权。这样的做法就是裸卖近期期权，如果股价反弹，就可能会有大量的亏损。&lt;/li&gt;
  &lt;li&gt;如果股票上涨，同样也有两种选择，一是立即平仓止损，另一种是什么也不做，等待近期期权到期时平仓。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;看多日历套利善后&quot;&gt;看多日历套利善后&lt;/h2&gt;

&lt;p&gt;对于看多日历套利，善后行动也是两种选择，一是平仓止损，另一种是什么也不做，等待近期期权到期时平仓。这个策略的特点是亏损比较小，但是如果有大笔盈利潜力，所以应该将仓位分散到多个标的上，只要少数盈利就可能弥补其他的小额亏损。这有点像一级市场的玩法。&lt;/p&gt;
</description>
        <pubDate>Fri, 26 Feb 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/02/26/option-strategy-calendar-spread/</link>
        <guid isPermaLink="true">https://yang.observer/2021/02/26/option-strategy-calendar-spread/</guid>
        
        <category>option investment strategy</category>
        
        <category>calendar spread</category>
        
        
      </item>
    
      <item>
        <title>期权投资策略（四）：ratio call</title>
        <description>&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#策略介绍&quot; id=&quot;markdown-toc-策略介绍&quot;&gt;策略介绍&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#策略变体&quot; id=&quot;markdown-toc-策略变体&quot;&gt;策略变体&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#善后行动&quot; id=&quot;markdown-toc-善后行动&quot;&gt;善后行动&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#移仓&quot; id=&quot;markdown-toc-移仓&quot;&gt;移仓&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#根据delta调整&quot; id=&quot;markdown-toc-根据delta调整&quot;&gt;根据delta调整&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍比率看涨期权立权(ratio call writing)相关的内容。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;风险提示：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;期权投资风险巨大，请不要轻易尝试。&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;策略介绍&quot;&gt;策略介绍&lt;/h1&gt;

&lt;p&gt;ratio call writing，中文叫比率看涨期权立权，是一个&lt;strong&gt;中性&lt;/strong&gt;组合策略，组合形式是&lt;strong&gt;买入股票，同时卖出2倍相应数量的call&lt;/strong&gt;。举个例子，股票XYZ现在价格是49，XYZ 10月50 call的价格是6，买入100股XYZ正股，同时卖出2张XYZ 10月50 call，这个组合就是ratio call。注意这里是卖出2张call，如果卖出1张XYZ 10月50 call就是前文介绍的covered call。我们来分析股票在不同价格时组合的表现：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果XYZ在10月到期时价格是37，call作废，获得权利金12点，同时由于正股下跌亏损了12点，此时在下行方向上盈亏平衡。如果股价继续下跌，权利金不足以弥补正股下跌的亏损，整个组合开始亏损。&lt;/li&gt;
  &lt;li&gt;如果XYZ在10月到期时价格是50，call作废，获得权利金12点，正股盈利1点，总共13点，这也是整个组合的最大盈利。&lt;/li&gt;
  &lt;li&gt;如果XYZ在10月到期时价格是63，call的价格是13，最初的卖出价是6点，每张亏损7点，总共14点，同时正股上涨盈利了14点，此时在上行方向上盈亏平衡。如果股价继续上涨，正股的盈利不足以弥补call的亏损，整个组合开始亏损。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;整个组合在[37, 63]的区间范围内是盈利的，超出这个范围亏损。最大盈利和上下行平衡点公式如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;最大盈利 = 行权价 - 股票价格 + 2*看涨期权价格
下行平衡点 = 行权价 - 最大盈利 = 股票价格 - 2*看涨期权价格
上行平衡点 = 行权价 + 最大盈利
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;组合的盈利图形如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-02-22-option-strategy-ratio-call/post-ratio-call1.png&quot; alt=&quot;图一&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从概率角度考虑，组合是一个高胜率的策略。因为概率上股价上下大幅波动的可能性非常小，很大概率到期时的股价在买入时的股价附近。大的亏损出现在远离当时股价的位置，而这个概率是很低的。盈利图形和概率分布曲线的叠加图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-02-22-option-strategy-ratio-call/post-ratio-call2.png&quot; alt=&quot;图二&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ratio call应该使用波动率较大的股票，因为权利金价格高，这样组合的盈利范围比较宽。&lt;/p&gt;

&lt;h1 id=&quot;策略变体&quot;&gt;策略变体&lt;/h1&gt;

&lt;p&gt;如果对股票略微看多，可以使用虚值期权，这样在上行方向比下行方向有更大的空间。反之，可以使用实值期权。&lt;/p&gt;

&lt;p&gt;举个例子， XYZ 10月45 call的价格是8，最大盈利是12，盈利区间是[33, 57]，XYZ 10月55 call的价格是4，最大盈利是14，盈利区间是[41, 69]。实值相当于整个盈利图形向左偏移了，虚值向右偏移。&lt;/p&gt;

&lt;p&gt;另外改变期权和股票的数量比例也可以建立略微看涨或者看空的头寸。一般期权和股票的比例是2:1，如果略微看空，可以卖出更多数量的期权。如果略微看涨，可以买进更多数量的正股。这种情况下的最大盈利和上下行平衡点公式如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;最大盈利 = （行权价 - 股票价格）* 股票数量 / 100 + 看涨期权数量 * 看涨期权价格
下行平衡点 = 行权价 - 最大盈利 / 100
上下平衡点 = 行权价 + 最大盈利 / （看涨期权数量 - 股票数量 / 100）
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比如按49买入100股XYZ，同时按6卖出3张XYZ 10月50 call，这时的最大盈利是18，盈利区间是[31, 59.5]。&lt;/p&gt;

&lt;p&gt;按49买入200股XYZ，同时按6卖出3张XYZ 10月50 call，这时的最大盈利是20，盈利区间是[40, 70]。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建仓时，如何才能确定组合是中性的？&lt;/strong&gt;在 &lt;a href=&quot;http://yang.observer/2021/02/15/option-strategy-call-buying/&quot;&gt;期权投资策略（二）：call buying&lt;/a&gt; 一文中介绍过delta的4个特点，其中一个特点就是delta可以表示组合是看涨还是看跌，正值表示看涨，负值看跌，0表示中性。在ratio call中，卖出期权，delta为负，买入正股相当于delta为正。1张期权也相当于delta*100数量的正股，那么中性数量的期权和正股比例就是1/delta。比如XYZ 10月50 call的delta是0.6，期权和正股的比例是1/0.6=5:3的时候，买入300正股，卖出5张call，组合是中性的。其实也没有必要完全保持中性，因为随着股价的变动和波动率的变动，很难时刻保持中性。&lt;/p&gt;

&lt;p&gt;如果股票价格正好在两个期权的行权价中间，可以使用这两个期权建立中性组合，这个策略称为变量比率立权(variable ratio write)。举个例子，XYZ 股票价格65，XYZ 10月60 call价格8，XYZ 10月70 call价格3。买入100股XYZ，同时卖出1张10月60 call和1张10月70 call，这个就是variable ratio write，也是中性头寸。&lt;/p&gt;

&lt;p&gt;到期时XYZ的股价在60和70之间的任何值，10月60的call都会被行权，10月70的call都会作废，所以在[60, 70]区间内，组合的盈利都是相同的，这也是组合的最大盈利。公式如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;最大盈利 = 总的期权权利金 + 低行权价 - 股票价格
下行平衡点 = 低行权价 - 最大盈利
上行平衡点 = 高行权价 + 最大盈利
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;盈利图形如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/2021-02-22-option-strategy-ratio-call/post-ratio-call3.png&quot; alt=&quot;图三&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;善后行动&quot;&gt;善后行动&lt;/h1&gt;

&lt;h2 id=&quot;移仓&quot;&gt;移仓&lt;/h2&gt;

&lt;p&gt;建仓ratio call后，如果股票价格快要到盈利区间的边缘，这时可以移仓，重新建立中性头寸。下面分别举股价下跌和上涨的例子。&lt;/p&gt;

&lt;p&gt;初始头寸：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;买入100股XYZ，价格49
卖出2张XYZ 10月50 call，价格6
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果股票下跌：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股票价格40
XYZ 10月50 call，价格1
XYZ 10月40 call，价格4
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时可以向下移仓到10月40 call，买回2张10月50 call，卖出2张10月40 call，这样从10月50 call上盈利了10点，相当于股票的成本变成了39，新的头寸相当于按39买入100股XYZ，按4卖出2张XYZ 10月40 call，也是一个中性头寸。如果股票继续大跌，可以继续使用这个办法移仓，游戏可以一直玩下去。&lt;/p&gt;

&lt;p&gt;如果股票上涨：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;XYZ股票价格60
XYZ 10月50 call，价格11
XYZ 10月60 call，价格6
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这时可以向上移仓到10月60 call，买回2张10月50 call，卖出2张10月60 call，这样从10月50 call上亏损了10点，相当于股票的成本变成了59，新的头寸相当于按59买入100股XYZ，按6卖出2张XYZ 10月60call，也是一个中性头寸。&lt;/p&gt;

&lt;p&gt;如果股价剧烈变动，超过了盈利区间，可以平仓整个头寸，避免进一步地亏损。&lt;/p&gt;

&lt;h2 id=&quot;根据delta调整&quot;&gt;根据delta调整&lt;/h2&gt;

&lt;p&gt;上面介绍了根据delta可以建仓一个中性的头寸，股价变化后，整个组合不再是中性的，这时可以根据期权新的delta来调整组合，保持组合的中性。&lt;/p&gt;

&lt;p&gt;举个例子，初始10月50 call的delta为0.6，期权和正股之间的比例应该是5:3，后来股票涨到57，10月50 call的delta变成了0.8，这时中性的期权和正股之间的比例应该是5:4，所以应该再买入100股正股来保持中性。另外也可以采用卖出少量期权的方式来保持比例。也就是说可以调整正股数量，也可以调整期权的数量。&lt;/p&gt;

&lt;p&gt;调整正股数量的公式为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;调整股票数量 = (新delta * 看涨期权数量 - 原有股票数量/100) * 100
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果计算出的数值为负，表示需要卖出正股。&lt;/p&gt;

&lt;p&gt;另外，这里介绍一个&lt;strong&gt;相等头寸(equivalent stock position, ESP)&lt;/strong&gt;的概念。相等头寸指的是期权相当于多少正股，ESP的计算公式为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;ESP = 期权数量 * delta * 1张期权对应的正股数
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;举个例子，买入10张XYZ 7月50 call，delta是0.45。ESP = 10 * 0.45 * 100 = 450。也就是说10手期权相当于450股正股。股票上涨1点，450股总共张了450点。期权上涨0.45点，10张也是450点。如果买入300股正股，同时卖出10张XYZ 7月50 call，这个组合就相当于做空150股正股，不是中性组合。&lt;/p&gt;
</description>
        <pubDate>Mon, 22 Feb 2021 00:00:00 +0000</pubDate>
        <link>https://yang.observer/2021/02/22/option-strategy-ratio-call/</link>
        <guid isPermaLink="true">https://yang.observer/2021/02/22/option-strategy-ratio-call/</guid>
        
        <category>option investment strategy</category>
        
        <category>ratio call</category>
        
        
      </item>
    
  </channel>
</rss>
