0%

## 寄生虫观后感

Posted on 2020-03-01 In 生活 , 影评 , 2020 Views: Disqus:

寄生虫这部电影在获得奥斯卡之前就有关注了，豆瓣评分很高，一开始看到这个片名以为是像《铁线虫入侵》那种灾难片，后来看到男主，宋康昊，也是老面孔了，从高中时候在学校操场组织看的《汉江怪物》，有点二的感觉，后来在大学寝室电脑上重新看的时候，室友跟我说是韩国国宝级演员，真人不可貌相，感觉是个呆子的形象。

但是你说这不是个灾难片，而是个反映社会问题的，就业比较容易往这个方向猜，只是剧情会是怎么样的，一时也没啥头绪，后来不知道哪里看了下一个剧情透露，是一个穷人给富人做家教，然后把自己一家都带进富人家，如果是这样的话可能会把这个怎么带进去作为一个主线，不过事实告诉我，这没那么重要，从第一步朋友的介绍，就显得无比顺利，要去当家教了，作为一个穷成这样的人，瞬间转变成一个衣着得体，言行举止都没让富人家看出破绽的延世大学学生，这真的挺难让人理解，所谓江山易改，本性难移，还有就是这人也正好有那么好能力去辅导，并且诡异的是，多惠也是瞬间就喜欢上了男主，多惠跟将男主介绍给她做家教，也就是多惠原来的家教敏赫，应该也有不少的相处时间，这变了有点大了吧，当然这里也可能因为时长需要，如果说这一点是因为时长，那可能我所有的槽点都是因为这个吧，因为我理解的应该是把家里的人如何一步步地带进富人家，这应该是整个剧情的一个需要更多铺垫去克服这个矛盾点，有时候也想过如果我去当导演，是能拍出个啥，没这个机会，可能有也会是很扯淡的，当然这也不能阻拦我谈谈对这个点的一些看法，毕竟评价一台电冰箱不是说我必须得自己会制冷对吧，这大概是我觉得这个电影的第一个槽点，接下去接二连三的，就是我说的这个最核心的矛盾点，不知道谁说过，这种影视剧应该是源自于生活又高于生活，越是好的作品，越要接近生活，这样子才更能有感同身受。

接下去的点是金基宇介绍金基婷去给多颂当美术家教，这一步又是我理解的败笔吧，就怎么说呢，没什么铺垫，突然从一个社会底层的穷姑娘，转变成一个气场爆表，把富人家太太唬得一愣一愣的，如果说富太太是比较简单无脑的，那富人自己应该是比较有见识而且是做 IT 的，给自己儿子女儿做家教的，查查底细也很正常吧，但是啥都没有，然后呢，她又开始耍司机的心机了，真的是莫名其妙了，司机真的很惨，窈窕淑女君子好逑，而且这个操作也让我摸不着头脑，这是多腹黑并且有经验才会这么操作，脱内裤真的是让我看得一愣愣的，更看得我一愣一愣的，富人竟然也完全按着这个思路去想了，完全没有别的可能呢，甚至可以去查下行车记录仪或者怎样的，或者有没有毛发体液啥的去检验下，毕竟金基婷也乘坐过这辆车，但是最最让我不懂的还是脱内裤这个操作，究竟是什么样的人才会的呢，值得思考。

金基泽和忠淑的点也是比较奇怪，首先是金基泽，引起最后那个杀人事件的一个由头，大部分观点都是人为朴社长在之前跟老婆啪啪啪的时候说金基泽的身上有股乘地铁的人的味道，简而言之就是穷人的味道，还有去雯光丈夫身下拿钥匙是对金基泽和雯光丈夫身上的味道的鄙夷，可是这个原因真的站不住脚，即使是同样经济水平，如果身上有比较重的异味，背后讨论下，或者闻到了比较重的味道，有不适的表情和动作很正常吧，像雯光丈夫，在地下室里呆了那么久，身上有异味并且比较重太正常了，就跟在厕所呆久了不会觉得味道大，但是从没味道的地方一进有点味道的厕所就会觉得异样，略尴尬的理由；再说忠淑呢，感觉是太厉害了，能胜任这么一家有钱人的各种挑剔的饮食口味要求的保姆职位，也是让人看懵逼了，看到了不禁想到一个问题，这家人开头是那么地穷，不堪，突然转变成这么地像骗子家族，如果有这么好的骗人能力，应该不会到这种地步吧，如果真的是那么穷，没能力，没志气，又怎么会突然变成这么厉害呢，一家人各司其职，把富人家唬得团团转，而这个前提是，这些人的确能胜任这四个位置，这就是我非常不能理解的点。

然后说回这个标题，寄生虫，不知道是不是翻译过来不准确，如果真的是叫寄生虫的话，这个寄生虫智商未免也太低了，没有像新冠那样机制，致死率低一点，传染能力强一点，潜伏期也能传染，这个寄生虫第一次受到免疫系统的攻击就自爆了；还有呢，作为一个社会比较低层的打工者，乡下人，对这个审题也是不太审的清，是指这一家人是社会的寄生虫，不思进取，并且死的应该，富人是傻白甜，又有钱又善良，这是给有钱人洗地了还是啥，这个奥斯卡真不知道是怎么得的，总觉得奥斯卡，甚至低一点，豆瓣，得奖的，评分高的都是被一群“精英党”把持的，有黑人主角的，得分高；有同性恋的，得分高；结局惨的，得分高；看不懂的，得分高；就像肖申克的救赎，真不知道是哪里好了，最近看了关于明朝那些事的三杨，杨溥的经历应该比这个厉害吧，可是外国人看不懂，就像外国人不懂中国为什么有反分裂国家法，经历了鸦片战争，八国联军，抗日战争等等，其实跟外国对于黑人的权益的问题，因为有南北战争，所以极度重视这个问题，相应的中国也有自己的历史，请理解。

简而言之我对寄生虫的评分大概 5~6 分吧。

## gogs使用webhook部署react单页应用

Posted on 2020-02-22 Edited on 2020-02-23 In 持续集成 Views: Disqus:

众所周知，我是个前端彩笔，但是也想做点自己可以用的工具页面，所以就让朋友推荐了蚂蚁出品的 ant design，说基本可以直接 ctrl-c ctrl-v，实测对我这种来说还是有点难的，不过也能写点，但是现在碰到的问题是怎么部署到自己的服务器上去
用 ant design 写的是个单页应用，实际来说就是一个 html 加 css 跟 js，最初的时候是直接 build 完就 scp 上去，也考虑过 rsync 之类的，但是都感觉不够自动化，正好自己还没这方面的经验就想折腾下，因为我自己搭的仓库应用是 gogs，搜了一下主要是跟 drones 配合做 ci/cd，研究了一下发现其实这个事情没必要这么搞(PS：drone 也不好用)，整个 hook 就可以了, 但是实际上呢，这东西也不是那么简单
首先是需要在服务器上装 webhook，这个我一开始用 snap 安装，但是出现问题，run 的时候会出现后面参数带的 hooks.json 文件找不到，然后索性就直接 github 上下最新版，放 /usr/local/bin 了，webhook 的原理呢其实也比较简单，就是起一个 http 服务，通过 post 请求调用，解析下参数，如果跟配置的参数一致，就调用对应的命令或者脚本。

## 配置 hooks.json

webhook 的配置，需要的两个文件，一个是 hooks.json，这个是 webhook 服务的配置文件，像这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    [
      {
        "id": "redeploy-app",
        "execute-command": "/opt/scripts/redeploy.sh",
        "command-working-directory": "/opt/scripts",
        "pass-arguments-to-command":
        [
          {
            "source": "payload",
            "name": "head_commit.message"
          },
          {
            "source": "payload",
            "name": "pusher.name"
          },
          {
            "source": "payload",
            "name": "head_commit.id"
          }
        ],
        "trigger-rule":
        {
          "and":
          [
            {
              "match":
              {
                "type": "payload-hash-sha1",
                "secret": "your-github-secret",
                "parameter":
                {
                  "source": "header",
                  "name": "X-Hub-Signature"
                }
              }
            },
            {
              "match":
              {
                "type": "value",
                "value": "refs/heads/master",
                "parameter":
                {
                  "source": "payload",
                  "name": "ref"
                }
              }
            }
          ]
        }
      }
    ]

这是个跟 github搭配的示例，首先 id 表示的是这个对应 hook 的识别 id，也可以看到这个 hooks.json 的结构是这样的一个数组，然后就是要执行的命令和命令执行的参数，值得注意的是这个`trigger-rule`,就是请求进来了回去匹配里面的，比如前一个是一个加密的，放在请求头里，第二个 match 表示请求里的 ref 是个 master 分支，就可以区分分支进行不同操作，但是前面的加密配合 gogs 使用的时候有个问题(PS: webhook 的文档是真的烂)，gogs 设置 webhook 的加密是用的

> 密钥文本将被用于计算推送内容的 SHA256 HMAC 哈希值，并设置为 `X-Gogs-Signature` 请求头的值。

这种加密方式，所以 webhook 的这个示例的加密方式不行，但这货的文档里居然没有说明支持哪些加密，神TM，后来还是翻 issue 翻到了, 需要使用这个`payload-hash-sha256`

## 执行脚本 redeploy.sh

脚本类似于这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/bin/bash -e

    function cleanup {
          echo "Error occoured"
    }
    trap cleanup ERR

    commit_message=$1 # head_commit.message
    pusher_name=$2 # pusher.name
    commit_id=$3 # head_commit.id

    cd ~/do-react-example-app/
    git pull origin master
    yarn && yarn build

就是简单的拉代码，然后构建下，真实使用时可能不是这样，因为页面会部署在 nginx 的作用目录，还需要 rsync 过去，这部分可能还涉及到两个问题第一个是使用 rsync 还是其他的 cp，不过这个无所谓；第二个是目录权限的问题，以我的系统ubuntu 为例，默认用户是 ubuntu，nginx 部署的目录是 www，所以需要切换用户等操作，一开始是想用在shell 文件中直接写了密码，但是不知道咋传，查了下是类似于这样 `echo "passwd" | sudo -S cmd`，通过管道命令往后传，然后就是这个`-S`, 参数的解释是`-S, --stdin read password from standard input`,但是这样么也不是太安全的赶脚，又看了下还有两种方法，

- 就是给root 设置一个不需要密码的命令类似于这样，

      1
      2
      myusername ALL = (ALL) ALL
      myusername ALL = (root) NOPASSWD: /path/to/my/program

- 另一种就是把默认用户跟 root 设置成同一个 group 的

## 使用

真正实操的时候其实还有不少问题，首先运行 webhook 就碰到了我前面说的，使用 snap 运行的时候会找不到前面的 hooks.json配置文件，执行`snap run webhook -hooks /opt/hooks/hooks.json -verbose`就碰到下面的`couldn't load hooks from file! open /opt/hooks/hooks.json: no such file or directory`，后来直接下了个官方最新的 release，就直接执行 `webhook -hooks /opt/hooks/hooks.json -verbose` 就可以了，然后是前面的示例配置文件里的几个参数，比如`head_commit.message` 其实 gogs 推过来的根本没这玩意，而且都是数组，不知道咋取，烂文档，不过总比搭个 drone 好一点就忍了。补充一点就是在 debug 的时候需要看下问题出在哪，看看脚本有没有执行，所以需要在前面的 json 里加这个参数`"include-command-output-in-response": true`, 就能输出来脚本执行结果

## Maven实用小技巧

Posted on 2020-02-16 In Java , Maven Views: Disqus:

Maven 翻译为”专家”、”内行”，是 Apache 下的一个纯 Java 开发的开源项目。基于项目对象模型（缩写：POM）概念，Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。

Maven 是一个项目管理工具，可以对 Java 项目进行构建、依赖管理。

Maven 也可被用于构建和管理各种项目，例如 C#，Ruby，Scala 和其他语言编写的项目。Maven 曾是 Jakarta 项目的子项目，现为由 Apache 软件基金会主持的独立 Apache 项目。

maven也是我们日常项目中实用的包管理工具，相比以前需要用把包下载下来，放进 lib 中，在平时工作中使用的话，其实像 idea 这样的 ide 工具都会自带 maven 工具和插件

### maven的基本操作

- `mvn -v`
  查看 maven 信息
- `mvn compile`
  将 Java 编译成 class 文件
- `mvn test`
  执行 test 包下的测试用例
- `mvn package`
  将项目打成 jar 包
- `mvn clean`
  删除package 在 target 目录下面打出来的 jar 包和 target 目录
- `mvn install`
  将打出来的 jar 包复制到 maven 的本地仓库里
- `mvn deploy`
  将打出来的 jar 包上传到远程仓库里

### 与 composer 对比

因为我也是个 PHP 程序员，所以对比一下两种语言，很容易想到在 PHP 的 composer 跟 Java 的 maven 是比较类似的作用，有一点两者是非常相似的，就是原仓库都是因为某些原因连接拉取都会很慢，所以像 composer 会有一些国内源，前阵子阿里也出了一个，类似的 maven 一般也会使用阿里的镜像仓库，通过在 setting.xml 文件中的设置

    1
    2
    3
    4
    5
    6
    7
    8
    <mirrors>
        <mirror>
            <id>aliyun</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>

这算是个尴尬的共同点，然后因为 PHP 是解释型脚本语言，所以 php 打出来的 composer 包其实就是个 php 代码包，使用SPL Autoload等方式加载代码包，maven 包则是经过编译的 class 包，还有一点是 composer 也可以直接使用 github 地址作为包代码的拉取源，这点也是比较大的区别，maven使用 pom 文件管理依赖

### maven 的个人小技巧

- maven 拉取依赖时，同时将 snapshot 也更新了，就是 `mvn compile`加个`-U`参数，如果还不行就需要将本地仓库的 snapshot 删掉，
  这个命令的 help 命令解释是 -U,–update-snapshots Forces a check for missing releases and updated snapshots on
  remote repositories，这个在日常使用中还是很经常使用的
- maven 出现依赖冲突的时候的解决方法
  首先是依赖分析，使用`mvn dependency:tree`分析下依赖关系，如果要找具体某个包的依赖引用关系可以使用`mvn dependency:tree -Dverbose -Dincludes=org.springframework:spring-webmvc`命令进行分析，如果发现有冲突的依赖关系，本身 maven 中依赖引用有相对的顺序，大致来说是引用路径短的优先，pom 文件中定义的顺序优先，如果要把冲突的包排除掉可以在 pom 中用

      1
      2
      3
      4
      5
      6
      <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
      </exclusions>

  将冲突的包排除掉
- maven 依赖的 jdk 版本管理
  前面介绍的`mvn -v`可以查看 maven 的安装信息
  比如

      1
      2
      3
      4
      5
      Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
      Maven home: /usr/local/Cellar/maven/3.6.3_1/libexec
      Java version: 1.8.0_201, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre
      Default locale: zh_CN, platform encoding: UTF-8
      OS name: "mac os x", version: "10.14.6", arch: "x86_64", family: "mac"

  这里可以看到用了 mac 自带的 jdk1.8，结合我之前碰到的一个问题，因为使用 homebrew 升级了 gradle，而 gradle 又依赖了 jdk13，因为这个 mvn 的 Java version 也变成 jdk13 了，然后 mvn 编译的时候出现了 `java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTags`这个问题，所以需要把这个版本给改回来，但是咋改呢，网上搜来的一大堆都是在 pom 文件里的
  source和 target 版本

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <build>
        <plugins>
      <plugin>
      	<groupId>org.apache.maven.plugins</groupId>
      	<artifactId>maven-compiler-plugin</artifactId>
      	<configuration>
      		<source>1.8</source>
      		<target>1.8</target>
      		<encoding>UTF-8</encoding>
      	</configuration>
      </plugin>
        </plugins>
      <build>

  或者修改 maven 的 setting.xml中的

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <profiles>
            <profile>
                <id>ngmm-nexus</id>
              <activation>
                <jdk>1.8</jdk>
              </activation>
              <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
                <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
            </properties>
            </profile>
      </profiles>

  但是这些都没啥用啊，真正有办法的是建个 `.mavenrc`，这个顾名思义就是 maven 的资源文件，类似于 `.bashrc`和`.zshrc`，在里面添加 MAVEN_HOME 和 JAVA_HOME,然后执行 `source .mavenrc`就 OK 啦

## G1收集器概述

Posted on 2020-02-09 In Java , JVM , C++ , GC Views: Disqus:

G1: The Garbage-First Collector, 垃圾回收优先的垃圾回收器，目标是用户多核 cpu 和大内存的机器，最大的特点就是可预测的停顿时间，官方给出的介绍是提供一个用户在大的堆内存情况下一个低延迟表现的解决方案，通常是 6GB 及以上的堆大小，有低于 0.5 秒稳定的可预测的停顿时间。

这里主要介绍这个比较新的垃圾回收器，在 G1 之前的垃圾回收器都是基于如下图的内存结构分布，有新生代，老年代和永久代（jdk8 之前），然后G1 往前的那些垃圾回收器都有个分代，比如 serial，parallel 等，一般有个应用的组合，最初的 serial 和 serial old，因为新生代和老年代的收集方式不太一样，新生代主要是标记复制，所以有 eden 跟两个 survival区，老年代一般用标记整理方式，而 G1 对这个不太一样。
看一下 G1 的内存分布
可以看到这有很大的不同，G1 通过将内存分成大小相等的 region，每个region是存在于一个连续的虚拟内存范围，对于某个 region 来说其角色是类似于原来的收集器的Eden、Survivor、Old Generation，这个具体在代码层面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // We encode the value of the heap region type so the generation can be
     // determined quickly. The tag is split into two parts:
     //
     //   major type (young, old, humongous, archive)           : top N-1 bits
     //   minor type (eden / survivor, starts / cont hum, etc.) : bottom 1 bit
     //
     // If there's need to increase the number of minor types in the
     // future, we'll have to increase the size of the latter and hence
     // decrease the size of the former.
     //
     // 00000 0 [ 0] Free
     //
     // 00001 0 [ 2] Young Mask
     // 00001 0 [ 2] Eden
     // 00001 1 [ 3] Survivor
     //
     // 00010 0 [ 4] Humongous Mask
     // 00100 0 [ 8] Pinned Mask
     // 00110 0 [12] Starts Humongous
     // 00110 1 [13] Continues Humongous
     //
     // 01000 0 [16] Old Mask
     //
     // 10000 0 [32] Archive Mask
     // 11100 0 [56] Open Archive
     // 11100 1 [57] Closed Archive
     //
     typedef enum {
       FreeTag               = 0,

       YoungMask             = 2,
       EdenTag               = YoungMask,
       SurvTag               = YoungMask + 1,

       HumongousMask         = 4,
       PinnedMask            = 8,
       StartsHumongousTag    = HumongousMask | PinnedMask,
       ContinuesHumongousTag = HumongousMask | PinnedMask + 1,

       OldMask               = 16,
       OldTag                = OldMask,

       // Archive regions are regions with immutable content (i.e. not reclaimed, and
       // not allocated into during regular operation). They differ in the kind of references
       // allowed for the contained objects:
       // - Closed archive regions form a separate self-contained (closed) object graph
       // within the set of all of these regions. No references outside of closed
       // archive regions are allowed.
       // - Open archive regions have no restrictions on the references of their objects.
       // Objects within these regions are allowed to have references to objects
       // contained in any other kind of regions.
       ArchiveMask           = 32,
       OpenArchiveTag        = ArchiveMask | PinnedMask | OldMask,
       ClosedArchiveTag      = ArchiveMask | PinnedMask | OldMask + 1
     } Tag;

`hotspot/share/gc/g1/heapRegionType.hpp`

当执行垃圾收集时，G1以类似于CMS收集器的方式运行。 G1执行并发全局标记阶段，以确定整个堆中对象的存活性。标记阶段完成后，G1知道哪些region是基本空的。它首先收集这些region，通常会产生大量的可用空间。这就是为什么这种垃圾收集方法称为“垃圾优先”的原因。顾名思义，G1将其收集和压缩活动集中在可能充满可回收对象（即垃圾）的堆区域。 G1使用暂停预测模型来满足用户定义的暂停时间目标，并根据指定的暂停时间目标选择要收集的区域数。

由G1标识为可回收的区域是使用撤离的方式(Evacuation)。 G1将对象从堆的一个或多个区域复制到堆上的单个区域，并在此过程中压缩并释放内存。撤离是在多处理器上并行执行的，以减少暂停时间并增加吞吐量。因此，对于每次垃圾收集，G1都在用户定义的暂停时间内连续工作以减少碎片。这是优于前面两种方法的。 CMS（并发标记扫描）垃圾收集器不进行压缩。 ParallelOld垃圾回收仅执行整个堆压缩，这导致相当长的暂停时间。

需要重点注意的是，G1不是实时收集器。它很有可能达到设定的暂停时间目标，但并非绝对确定。 G1根据先前收集的数据，估算在用户指定的目标时间内可以收集多少个区域。因此，收集器具有收集区域成本的合理准确的模型，并且收集器使用此模型来确定要收集哪些和多少个区域，同时保持在暂停时间目标之内。

注意：G1同时具有并发（与应用程序线程一起运行，例如优化，标记，清理）和并行（多线程，例如stw）阶段。Full GC仍然是单线程的，但是如果正确调优，您的应用程序应该可以避免Full GC。

在前面那篇中在代码层面简单的了解了这个可预测时间的过程，这也是 G1 的一大特点。

## 2019年终总结

Posted on 2020-02-01 In 生活 , 年终总结 , 2019 Views: Disqus:

今天是农历初八了，年前一个月的时候就准备做下今年的年终总结，可是写了一点觉得太情绪化了，希望后面写个平淡点的，正好最近技术方面还没有看到一个完整成文的内容，就来写一下这一年的总结，尽量少写一点太情绪化的东西。

### 跳槽

年初换了个公司，也算换了个环境，跟前公司不太一样，做的事情方向也不同，可能是侧重点不同，一开始有些不适应，主要是压力上，会觉得压力比较大，但是总体来说与人相处的部分还是不错的，做的技术方向还是Java，这里也感谢前东家让我有机会转了Java，个人感觉杭州整个市场还是Java比较有优势，不过在开始的时候总觉得对Java有点不适应，应该值得深究的东西还是很多的，而且对于面试来说，也是有很多可以问的，后面慢慢发现除开某里等一线超一线互联网公司之外，大部分的面试还是有大概的套路跟大纲的，不过更细致的则因人而异了，面试有时候也还看缘分，面试官关注的点跟应试者比较契合的话就很容易通过面试，不然的话总会有能刁难或者理性化地说比较难回答的问题。这个后面可以单独说一下，先按下不表。
刚进公司没多久就负责比较重要的项目，工期也比较紧张，整体来说那段时间的压力的确是比较大的，不过总算最后结果不坏，这里应该说对一些原来在前东家都是掌握的不太好的部分，比如maven，其实maven对于java程序员来说还是很重要的，但是我碰到过的面试基本没问过这个，我自己也在后面的面试中没问过相关的，不知道咋问，比如dependence分析、冲突解决，比如对bean的理解，这个算是我一直以来的疑问点，因为以前刚开始学Java学spring，上来就是bean，但是bean到底是啥，IOC是啥，可能网上的文章跟大多数书籍跟我的理解思路不太match，导致一直不能很好的理解这玩意，到后面才理解，要理解这个bean，需要有两个基本概念，一个是面向对象，一个是对象容器跟依赖反转，还是只说到这，后面可以有专题说一下，总之自认为技术上有了不小的长进了，方向上应该是偏实用的。这个重要的项目完成后慢慢能喘口气了，后面也有一些比较紧急且工作量大的，不过在我TL的帮助下还是能尽量协调好资源。

### 面试

后面因为项目比较多，缺少开发，所以也参与帮忙做一些面试，这里总体感觉是面的候选人还是比较多样的，有些工作了蛮多年但是一些基础问题回答的不好，有些还是在校学生，但是面试技巧不错，针对常见的面试题都有不错的准备，不过还是觉得光靠这些面试题不能完全说明问题，真正工作了需要的是解决问题的人，而不是会背题的，退一步来说能好好准备面试还是比较重要的，也是双向选择中的基本尊重，印象比较深刻的是参加了去杭州某高校的校招面试，感觉参加校招的同学还是很多的，大部分是20年将毕业的研究生，挺多都是基础很扎实，对比起我刚要毕业时还是很汗颜，挺多来面试的同学都非常不错，那天强度也很大，从下午到那开始一直面到六七点，在这祝福那些来面试的同学，也都不容易的，能找到心仪的工作。

### 技术方向

这一年前大半部分还是比较焦虑不能恢复那种主动找时间学习的状态，可能换了公司是主要的原因，初期有个适应的过程也比较正常，总体来说可能是到九十月份开始慢慢有所改善，对这些方面有学习了下，

- spring方向，spring真的是个庞然大物，但是还是要先抓住根本，慢慢发散去了解其他的细节，抓住bean的生命周期，当然也不是死记硬背，让我一个个背下来我也不行，但是知道它究竟是干嘛的，有啥用，并且在工作中能用起来是最重要的
- mysql数据库，这部分主要是关注了mvcc，知道了个大概，源码实现细节还没具体研究，有时间可以来个专题（一大堆待写的内容）
- java的一些源码，比如aqs这种，结合文章看了下源码，一开始总感觉静不下心来看，然后有一次被LD刺激了下就看完了，包括conditionObject等
- redis的源码，这里包括了Redis分布式锁和redis的数据结构源码，已经写成文章，不过比较着急成文，所以质量不是特别好，希望后面再来补补
- jvm源码，这部分正好是想了解下g1收集器，大概把周志明的书看完了，但是还没完整的理解掌握，还有就是g1收集器的部分，一是概念部分大概理解了，后面是就是想从源码层面去学习理解，这也是新一年的主要计划
- mq的部分是了解了zero copy，sendfile等，跟消息队列主题关系不大🤦‍♂️
  这么看还是学了点东西的，希望新一年再接再厉。

### 生活

住的地方没变化，主要是周边设施比较方便，暂时没找到更好的就没打算换，主要的问题是没电梯，一开始没觉得有啥，真正住起来还是觉得比较累的，希望后面租的可以有电梯，或者楼层低一点，还有就是要通下水道，第一次让师傅上门，花了两百大洋，后来自学成才了，让师傅通了一次才撑了一个月就不行了，后面自己通的差不多可以撑半年，还是比较有成就感的😀，然后就是跑步了，年初的时候去了紫金港跑步，后面因为工作的原因没去了，但是公司的跑步机倒是让我重拾起这个唯一的运动健身项目，后面因为肠胃问题，体重也需要控制，所以就周末回来也在家这边坚持跑步，下半年的话基本保持每周一次以上，比较那些跑马拉松的大牛还是差距很大，不过也是突破自我了，有一次跑了12公里，最远的距离，而且后面感觉跑十公里也不是特别吃不消了，这一年达成了300公里的目标，体重也稍有下降，比较满意的结果。

### 期待

希望工作方面技术方面能有所长进，生活上能多点时间陪家人，继续跑步减肥，家人健健康康的，嗯

## redis数据结构介绍六 快表

Posted on 2020-01-22 Edited on 2020-01-26 In Redis , 数据结构 , C , 源码 , Redis Views: Disqus:

这应该是 redis 系列的最后一篇了，讲下快表，其实最前面讲的链表在早先的 redis 版本中也作为 list 的数据结构使用过，但是单纯的链表的缺陷之前也说了，插入便利，但是空间利用率低，并且不能进行二分查找等，检索效率低，ziplist 压缩表的产生也是同理，希望获得更好的性能，包括存储空间和访问性能等，原来我也不懂这个快表要怎么快，然后明白了一个道理，其实并没有什么银弹，只是大牛们会在适合的时候使用最适合的数据结构来实现性能的最大化，这里面有一招就是不同数据结构的组合调整，比如 Java 中的 HashMap，在链表节点数大于 8 时会转变成红黑树，以此提高访问效率，不费话了，回到快表，quicklist，这个数据结构主要使用在 list 类型中，如果我说其实这个 quicklist 就是个链表，可能大家不太会相信，但是事实上的确可以认为 quicklist 是个双向链表，看下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    /* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
     * We use bit fields keep the quicklistNode at 32 bytes.
     * count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
     * encoding: 2 bits, RAW=1, LZF=2.
     * container: 2 bits, NONE=1, ZIPLIST=2.
     * recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
     * attempted_compress: 1 bit, boolean, used for verifying during testing.
     * extra: 10 bits, free for future use; pads out the remainder of 32 bits */
    typedef struct quicklistNode {
        struct quicklistNode *prev;
        struct quicklistNode *next;
        unsigned char *zl;
        unsigned int sz;             /* ziplist size in bytes */
        unsigned int count : 16;     /* count of items in ziplist */
        unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
        unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
        unsigned int recompress : 1; /* was this node previous compressed? */
        unsigned int attempted_compress : 1; /* node can't compress; too small */
        unsigned int extra : 10; /* more bits to steal for future usage */
    } quicklistNode;

    /* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
     * 'sz' is byte length of 'compressed' field.
     * 'compressed' is LZF data with total (compressed) length 'sz'
     * NOTE: uncompressed length is stored in quicklistNode->sz.
     * When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
    typedef struct quicklistLZF {
        unsigned int sz; /* LZF size in bytes*/
        char compressed[];
    } quicklistLZF;

    /* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
     * 'count' is the number of total entries.
     * 'len' is the number of quicklist nodes.
     * 'compress' is: -1 if compression disabled, otherwise it's the number
     *                of quicklistNodes to leave uncompressed at ends of quicklist.
     * 'fill' is the user-requested (or default) fill factor. */
    typedef struct quicklist {
        quicklistNode *head;
        quicklistNode *tail;
        unsigned long count;        /* total count of all entries in all ziplists */
        unsigned long len;          /* number of quicklistNodes */
        int fill : 16;              /* fill factor for individual nodes */
        unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
    } quicklist;

粗略看下，quicklist 里有 head，tail, quicklistNode里有 prev，next 指针，是不是有链表的基本轮廓了，那么为啥这玩意要称为快表呢，快在哪，关键就在这个`unsigned char *zl;`zl 是不是前面又看到过，就是 ziplist ，这是什么鬼，链表里用压缩表，这不套娃么，先别急，回顾下前面说的 ziplist，ziplist 有哪些特点，内存利用率高，可以从表头快速定位到尾节点，节点可以从后往前找，但是有个缺点，就是从中间插入的效率比较低，需要整体往后移，这个其实是普通数组的优化版，但还是有数组的一些劣势，所以要真的快，是不是可以将链表跟数组真的结合起来。

## ziplist

这里有两个 redis 的配置参数，`list-max-ziplist-size` 和 `list-compress-depth`，先来说第一个，既然快表是将链表跟压缩表数组结合起来使用，那么具体怎么用呢，比如我有一个 10 个元素的 list，那具体怎么放，每个 quicklistNode 里放多大的 ziplist，假如每个快表节点的 ziplist 只放一个元素，那么其实这就退化成了一个链表，如果 10 个元素放在一个 quicklistNode 的 ziplist 里，那就退化成了一个 ziplist，所以有了这个 `list-max-ziplist-size`,而且它还比较牛，能取正负值，当是正值时，对应的就是每个 quicklistNode 的 ziplist 中的元素个数，比如配置了 `list-max-ziplist-size = 5`，那么我刚才的 10 个元素的 list 就是一个两个 quicklistNode 组成的快表，每个 quicklistNode 中的 ziplist 包含了五个元素，当 `list-max-ziplist-size`取负值的时候，它限制了 ziplist 的字节数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    size_t offset = (-fill) - 1;
    if (offset < (sizeof(optimization_level) / sizeof(*optimization_level))) {
        if (sz <= optimization_level[offset]) {
            return 1;
        } else {
            return 0;
        }
    } else {
        return 0;
    }

    /* Optimization levels for size-based filling */
    static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};

    /* Create a new quicklist.
     * Free with quicklistRelease(). */
    quicklist *quicklistCreate(void) {
        struct quicklist *quicklist;

        quicklist = zmalloc(sizeof(*quicklist));
        quicklist->head = quicklist->tail = NULL;
        quicklist->len = 0;
        quicklist->count = 0;
        quicklist->compress = 0;
        quicklist->fill = -2;
        return quicklist;
    }

这个 fill 就是传进来的 `list-max-ziplist-size`, 具体对应的就是

- -5: 每个quicklist节点上的ziplist大小不能超过64 Kb。（注：1kb => 1024 bytes）
- -4: 每个quicklist节点上的ziplist大小不能超过32 Kb。
- -3: 每个quicklist节点上的ziplist大小不能超过16 Kb。
- -2: 每个quicklist节点上的ziplist大小不能超过8 Kb。（-2是Redis给出的默认值）也就是上面的 `quicklist->fill = -2;`
- -1: 每个quicklist节点上的ziplist大小不能超过4 Kb。

## 压缩

`list-compress-depth`这个参数呢是用来配置压缩的，等等压缩是为啥，不是里面已经是压缩表了么，大牛们就是为了性能殚精竭虑，这里考虑到的是一个场景，一般状况下，list 都是两端的访问频率比较高，那么是不是可以对中间的数据进行压缩，那么这个参数就是用来表示

    1
    /* depth of end nodes not to compress;0=off */

- 0，代表不压缩，默认值
- 1，两端各一个节点不压缩
- 2，两端各两个节点不压缩
- … 依次类推
  压缩后的 ziplist 就会变成 quicklistLZF，然后替换 zl 指针，这里使用的是 LZF 压缩算法，压缩后的 quicklistLZF 中的 compressed 也是个柔性数组，压缩后的 ziplist 整个就放进这个柔性数组

## 插入过程

简单说下插入元素的过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    /* Wrapper to allow argument-based switching between HEAD/TAIL pop */
    void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
                       int where) {
        if (where == QUICKLIST_HEAD) {
            quicklistPushHead(quicklist, value, sz);
        } else if (where == QUICKLIST_TAIL) {
            quicklistPushTail(quicklist, value, sz);
        }
    }

    /* Add new entry to head node of quicklist.
     *
     * Returns 0 if used existing head.
     * Returns 1 if new head created. */
    int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
        quicklistNode *orig_head = quicklist->head;
        if (likely(
                _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
            quicklist->head->zl =
                ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
            quicklistNodeUpdateSz(quicklist->head);
        } else {
            quicklistNode *node = quicklistCreateNode();
            node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);

            quicklistNodeUpdateSz(node);
            _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
        }
        quicklist->count++;
        quicklist->head->count++;
        return (orig_head != quicklist->head);
    }

    /* Add new entry to tail node of quicklist.
     *
     * Returns 0 if used existing tail.
     * Returns 1 if new tail created. */
    int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
        quicklistNode *orig_tail = quicklist->tail;
        if (likely(
                _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
            quicklist->tail->zl =
                ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
            quicklistNodeUpdateSz(quicklist->tail);
        } else {
            quicklistNode *node = quicklistCreateNode();
            node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);

            quicklistNodeUpdateSz(node);
            _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
        }
        quicklist->count++;
        quicklist->tail->count++;
        return (orig_tail != quicklist->tail);
    }

    /* Wrappers for node inserting around existing node. */
    REDIS_STATIC void _quicklistInsertNodeBefore(quicklist *quicklist,
                                                 quicklistNode *old_node,
                                                 quicklistNode *new_node) {
        __quicklistInsertNode(quicklist, old_node, new_node, 0);
    }

    REDIS_STATIC void _quicklistInsertNodeAfter(quicklist *quicklist,
                                                quicklistNode *old_node,
                                                quicklistNode *new_node) {
        __quicklistInsertNode(quicklist, old_node, new_node, 1);
    }

    /* Insert 'new_node' after 'old_node' if 'after' is 1.
     * Insert 'new_node' before 'old_node' if 'after' is 0.
     * Note: 'new_node' is *always* uncompressed, so if we assign it to
     *       head or tail, we do not need to uncompress it. */
    REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
                                            quicklistNode *old_node,
                                            quicklistNode *new_node, int after) {
        if (after) {
            new_node->prev = old_node;
            if (old_node) {
                new_node->next = old_node->next;
                if (old_node->next)
                    old_node->next->prev = new_node;
                old_node->next = new_node;
            }
            if (quicklist->tail == old_node)
                quicklist->tail = new_node;
        } else {
            new_node->next = old_node;
            if (old_node) {
                new_node->prev = old_node->prev;
                if (old_node->prev)
                    old_node->prev->next = new_node;
                old_node->prev = new_node;
            }
            if (quicklist->head == old_node)
                quicklist->head = new_node;
        }
        /* If this insert creates the only element so far, initialize head/tail. */
        if (quicklist->len == 0) {
            quicklist->head = quicklist->tail = new_node;
        }

        if (old_node)
            quicklistCompress(quicklist, old_node);

        quicklist->len++;
    }

前面第一步先根据插入的是头还是尾选择不同的 push 函数，quicklistPushHead 或者 quicklistPushTail，举例分析下从头插入的 quicklistPushHead，先判断当前的 quicklistNode 节点还能不能允许再往 ziplist 里添加元素，如果可以就添加，如果不允许就新建一个 quicklistNode，然后调用 _quicklistInsertNodeBefore 将节点插进去，具体插入quicklist节点的操作类似链表的插入。

## redis数据结构介绍五-第五部分 对象

Posted on 2020-01-20 In Redis , 数据结构 , C , 源码 , Redis Views: Disqus:

前面说了这么些数据结构，其实大家对于 redis 最初的印象应该就是个 key-value 的缓存，类似于 memcache，redis 其实也是个 key-value，key 还是一样的字符串，或者说就是用 redis 自己的动态字符串实现，但是 value 其实就是前面说的那些数据结构，差不多快说完了，还有个 quicklist 后面还有一篇，这里先介绍下 redis 对于这些不同类型的 value 是怎么实现的，首先看下 redisObject 的源码头文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    /* The actual Redis Object */
    #define OBJ_STRING 0    /* String object. */
    #define OBJ_LIST 1      /* List object. */
    #define OBJ_SET 2       /* Set object. */
    #define OBJ_ZSET 3      /* Sorted set object. */
    #define OBJ_HASH 4      /* Hash object. */
    /*
     * Objects encoding. Some kind of objects like Strings and Hashes can be
     * internally represented in multiple ways. The 'encoding' field of the object
     * is set to one of this fields for this object. */
    #define OBJ_ENCODING_RAW 0     /* Raw representation */
    #define OBJ_ENCODING_INT 1     /* Encoded as integer */
    #define OBJ_ENCODING_HT 2      /* Encoded as hash table */
    #define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
    #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
    #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    #define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
    #define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
    #define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
    #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
    #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

    #define LRU_BITS 24
    #define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
    #define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

    #define OBJ_SHARED_REFCOUNT INT_MAX
    typedef struct redisObject {
        unsigned type:4;
        unsigned encoding:4;
        unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                                * LFU data (least significant 8 bits frequency
                                * and most significant 16 bits access time). */
        int refcount;
        void *ptr;
    } robj;

主体结构就是这个 redisObject，

- type： 字段表示对象的类型，它对应的就是 redis 的对外暴露的，或者说用户可以使用的五种类型，OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH
- encoding： 字段表示这个对象在 redis 内部的编码方式，由OBJ_ENCODING_开头的 11 种
- lru: 做LRU替换算法用，占24个bit
- refcount: 引用计数。它允许robj对象在某些情况下被共享。
- ptr: 指向底层实现数据结构的指针
  当 type 是 OBJ_STRING 时，表示类型是个 string，它的编码方式 encoding 可能有 OBJ_ENCODING_RAW，OBJ_ENCODING_INT，OBJ_ENCODING_EMBSTR 三种
  当 type 是 OBJ_LIST 时，表示类型是 list，它的编码方式 encoding 是 OBJ_ENCODING_QUICKLIST，对于早一些的版本，2.2这种可能还会使用 OBJ_ENCODING_ZIPLIST，OBJ_ENCODING_LINKEDLIST
  当 type 是 OBJ_SET 时，是个集合，但是得看具体元素的类型，有可能使用整数集合，OBJ_ENCODING_INTSET, 如果元素不全是整型或者数量超过一定限制，那么编码就是 OBJ_ENCODING_HT hash table 了
  当 type 是 OBJ_ZSET 时，是个有序集合，它底层有可能使用的是 OBJ_ENCODING_ZIPLIST 或者 OBJ_ENCODING_SKIPLIST
  当 type 是 OBJ_HASH 时，一开始也是 OBJ_ENCODING_ZIPLIST，然后当数据量大于 hash_max_ziplist_entries 时会转成 OBJ_ENCODING_HT

## redis数据结构介绍四-第四部分 压缩表

Posted on 2020-01-19 In Redis , 数据结构 , C , 源码 , Redis Views: Disqus:

在 redis 中还有一类表型数据结构叫压缩表，ziplist，它的目的是替代链表，链表是个很容易理解的数据结构，双向链表有前后指针，有带头结点的有的不带，但是链表有个比较大的问题是相对于普通的数组，它的内存不连续，碎片化的存储，内存利用效率不高，而且指针寻址相对于直接使用偏移量的话，也有一定的效率劣势，当然这不是主要的原因，ziplist 设计的主要目的是让链表的内存使用更高效

> The ziplist is a specially encoded dually linked list that is designed to be very memory efficient.
> 这是摘自 redis 源码中ziplist.c 文件的注释，也说明了原因，它的大概结构是这样子

    1
    <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

其中
`<zlbytes>`表示 ziplist 占用的字节总数，类型是uint32_t，32 位的无符号整型，当然表示的字节数也包含自己本身占用的 4 个
`<zltail>` 类型也是是uint32_t，表示ziplist表中最后一项（entry）在ziplist中的偏移字节数。`<zltail>`的存在，使得我们可以很方便地找到最后一项（不用遍历整个ziplist），从而可以在ziplist尾端快速地执行push或pop操作。
`<uint16_t zllen>` 表示ziplist 中的数据项个数，因为是 16 位，所以当数量超过所能表示的最大的数量，它的 16 位全会置为 1，但是真实的数量需要遍历整个 ziplist 才能知道
`<entry>`是具体的数据项，后面解释
`<zlend>` ziplist 的最后一个字节，固定是255。
再看一下`<entry>`中的具体结构，

    1
    <prevlen> <encoding> <entry-data>

首先这个`<prevlen>`有两种情况，一种是前面的元素的长度，如果是小于等于 253的时候就用一个uint8_t 来表示前一元素的长度，如果大于的话他将占用五个字节，第一个字节是 254，即表示这个字节已经表示不下了，需要后面的四个字节帮忙表示
`<encoding>`这个就比较复杂，把源码的注释放下面先看下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    * |00pppppp| - 1 byte
    *      String value with length less than or equal to 63 bytes (6 bits).
    *      "pppppp" represents the unsigned 6 bit length.
    * |01pppppp|qqqqqqqq| - 2 bytes
    *      String value with length less than or equal to 16383 bytes (14 bits).
    *      IMPORTANT: The 14 bit number is stored in big endian.
    * |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
    *      String value with length greater than or equal to 16384 bytes.
    *      Only the 4 bytes following the first byte represents the length
    *      up to 32^2-1. The 6 lower bits of the first byte are not used and
    *      are set to zero.
    *      IMPORTANT: The 32 bit number is stored in big endian.
    * |11000000| - 3 bytes
    *      Integer encoded as int16_t (2 bytes).
    * |11010000| - 5 bytes
    *      Integer encoded as int32_t (4 bytes).
    * |11100000| - 9 bytes
    *      Integer encoded as int64_t (8 bytes).
    * |11110000| - 4 bytes
    *      Integer encoded as 24 bit signed (3 bytes).
    * |11111110| - 2 bytes
    *      Integer encoded as 8 bit signed (1 byte).
    * |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
    *      Unsigned integer from 0 to 12. The encoded value is actually from
    *      1 to 13 because 0000 and 1111 can not be used, so 1 should be
    *      subtracted from the encoded 4 bit value to obtain the right value.
    * |11111111| - End of ziplist special entry.

首先如果 encoding 的前两位是 00 的话代表这个元素是个 6 位的字符串，即直接将数据保存在 encoding 中，不消耗额外的`<entry-data>`，如果前两位是 01 的话表示是个 14 位的字符串，如果是 10 的话表示encoding 块之后的四个字节是存放字符串类型的数据，encoding 的剩余 6 位置 0。
如果 encoding 的前两位是 11 的话表示这是个整型，具体的如果后两位是00的话，表示后面是个2字节的 int16_t 类型，如果是01的话，后面是个4字节的int32_t,如果是10的话后面是8字节的int64_t,如果是 11 的话后面是 3 字节的有符号整型，这些都要最后 4 位都是 0 的情况噢
剩下当是`11111110`时，则表示是一个1 字节的有符号数，如果是 `1111xxxx`，其中`xxxx`在0000 到 1101 表示实际的 1 到 13，为啥呢，因为 0000 前面已经用过了，而 1110 跟 1111 也都有用了。
看个具体的例子(上下有点对不齐，将就看)

    1
    2
    [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
    |**zlbytes***|  |***zltail***|  |*zllen*|  |entry1 entry2|  |zlend|

第一部分代表整个 ziplist 有 15 个字节，zlbytes 自己占了 4 个 zltail 表示最后一个元素的偏移量，第 13 个字节起，zllen 表示有 2 个元素，第一个元素是`00f3`,00表示前一个元素长度是 0，本来前面就没元素(不过不知道这个能不能优化这一字节)，然后是 f3，换成二进制就是11110011,对照上面的注释，是落在|1111xxxx|这个类型里，注意这个其实是用 0001 到 1101 也就是 1到 13 来表示 0到 12，所以 f3 应该就是 2，第一个元素是 2，第二个元素呢，02 代表前一个元素也就是刚才说的这个，占用 2 字节，f6 展开也是刚才的类型，实际是 5，ff 表示 ziplist 的结尾，所以这个 ziplist 里面是两个元素，2 跟 5

## redis数据结构介绍三-第三部分 整数集合

Posted on 2020-01-10 Edited on 2020-01-12 In Redis , 数据结构 , C , 源码 , Redis Views: Disqus:

redis中对于 set 其实有两种处理，对于元素均为整型，并且元素数目较少时，使用 intset 作为底层数据结构，否则使用 dict 作为底层数据结构，先看一下代码先

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    typedef struct intset {
        // 编码方式
        uint32_t encoding;
        // 集合包含的元素数量
        uint32_t length;
        // 保存元素的数组
        int8_t contents[];
    } intset;

    /* Note that these encodings are ordered, so:
     * INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
    #define INTSET_ENC_INT16 (sizeof(int16_t))
    #define INTSET_ENC_INT32 (sizeof(int32_t))
    #define INTSET_ENC_INT64 (sizeof(int64_t))

一眼看，为啥整型还需要编码，然后 int8_t 怎么能存下大整形呢，带着这些疑问，我们一步步分析下去，这里的编码其实指的是这个整型集合里存的究竟是多大的整型，16 位，还是 32 位，还是 64 位，结构体下面的宏定义就是表示了 encoding 的可能取值，INTSET_ENC_INT16 表示每个元素用2个字节存储，INTSET_ENC_INT32 表示每个元素用4个字节存储，INTSET_ENC_INT64 表示每个元素用8个字节存储。因此，intset中存储的整数最多只能占用64bit。length 就是正常的表示集合中元素的数量。最奇怪的应该就是这个 contents 了，是个 int8_t 的数组，那放毛线数据啊，最小的都有 16 位，这里我在看代码和《redis 设计与实现》的时候也有点懵逼，后来查了下发现这是个比较取巧的用法，这里我用自己的理解表述一下，先看看 8，16，32，64 的关系，一眼看就知道都是 2 的 N 次，并且呈两倍关系，而且 8 位刚好一个字节，所以呢其实这里的contents 不是个常规意义上的 int8_t 类型的数组，而是个柔性数组。看下 wiki 的定义

> Flexible array members1 were introduced in the C99 standard of the C programming language (in particular, in section §6.7.2.1, item 16, page 103).2 It is a member of a struct, which is an array without a given dimension. It must be the last member of such a struct and it must be accompanied by at least one other member, as in the following example:

    1
    2
    3
    4
    struct vectord {
        size_t len;
        double arr[]; // the flexible array member must be last
    };

在初始化这个 intset 的时候，这个contents数组是不占用空间的，后面的反正用到了申请，那么这里就有一个问题，给出了三种可能的 encoding 值，他们能随便换吗，显然不行，首先在 intset 中数据的存放是有序的，这个有部分原因是方便二分查找，然后存放数据其实随着数据的大小不同会有一个升级的过程，看下图
新创建的intset只有一个header，总共8个字节。其中encoding = 2, length = 0, 类型都是uint32_t，各占 4 字节，添加15, 5两个元素之后，因为它们是比较小的整数，都能使用2个字节表示，所以encoding不变，值还是2，也就是默认的 `INTSET_ENC_INT16`，当添加32768的时候，它不再能用2个字节来表示了（2个字节能表达的数据范围是-215~215-1，而32768等于215，超出范围了），因此encoding必须升级到INTSET_ENC_INT32（值为4），即用4个字节表示一个元素。在添加每个元素的过程中，intset始终保持从小到大有序。与ziplist类似，intset也是按小端（little endian）模式存储的（参见维基百科词条Endianness）。比如，在上图中intset添加完所有数据之后，表示encoding字段的4个字节应该解释成0x00000004，而第4个数据应该解释成0x00008000 = 32768

## redis数据结构介绍二-第二部分 跳表

Posted on 2020-01-04 Edited on 2020-01-12 In Redis , 数据结构 , C , 源码 , Redis Views: Disqus:

## 跳表 skiplist

跳表是个在我们日常的代码中不太常用到的数据结构，相对来讲就没有像数组，链表，字典，散列，树等结构那么熟悉，所以就从头开始分析下，首先是链表，跳表跟链表都有个表字（太硬扯了我🤦‍♀️），注意这是个有序链表
如上图，在这个链表里如果我要找到 23，是不是我需要从3，5，9开始一直往后找直到找到 23，也就是说时间复杂度是 O(N),N 的一次幂复杂度，那么我们来看看第二个
这个结构跟原先有点不一样，它给链表中偶数位的节点又加了一个指针把它们链接起来，这样子当我们要寻找 23 的时候就可以从原来的一个个往下找变成跳着找，先找到 5，然后是 10，接着是 19，然后是 28，这时候发现 28 比 23 大了，那我在退回到 19，然后从下一层原来的链表往前找，
这里毛估估是不是前面的节点我就少找了一半,有那么点二分法的意思。
前面的其实是跳表的引子，真正的跳表其实不是这样，因为上面的其实有个比较大的问题，就是插入一个元素后需要调整每个元素的指针，在 redis 中的跳表其实是做了个随机层数的优化，因为沿着前面的例子，其实当数据量很大的时候，是不是层数越多，其查询效率越高，但是随着层数变多，要保持这种严格的层数规则其实也会增大处理复杂度，所以 redis 插入每个元素的时候都是使用随机的方式，看一眼代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /* ZSETs use a specialized version of Skiplists */
    typedef struct zskiplistNode {
        sds ele;
        double score;
        struct zskiplistNode *backward;
        struct zskiplistLevel {
            struct zskiplistNode *forward;
            unsigned long span;
        } level[];
    } zskiplistNode;

    typedef struct zskiplist {
        struct zskiplistNode *header, *tail;
        unsigned long length;
        int level;
    } zskiplist;

    typedef struct zset {
        dict *dict;
        zskiplist *zsl;
    } zset;

忘了说了，redis 是把 skiplist 跳表用在 zset 里，zset 是个有序的集合，可以看到 zskiplist 就是个跳表的结构，里面用 header 保存跳表的表头，tail 保存表尾，还有长度和最大层级，具体的跳表节点元素使用 zskiplistNode 表示，里面包含了 sds 类型的元素值，double 类型的分值，用来排序，一个 backward 后向指针和一个 zskiplistLevel 数组，每个 level 包含了一个前向指针，和一个 span，span 表示的是跳表前向指针的跨度，这里再补充一点，前面说了为了灵活这个跳表的新增修改，redis 使用了随机层高的方式插入新节点，但是如果所有节点都随机到很高的层级或者所有都很低的话，跳表的效率优势就会减小，所以 redis 使用了个小技巧，贴下代码

    1
    2
    3
    4
    5
    6
    7
    #define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
    int zslRandomLevel(void) {
        int level = 1;
        while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
            level += 1;
        return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
    }

当随机值跟0xFFFF进行与操作小于ZSKIPLIST_P * 0xFFFF时才会增大 level 的值，因此保持了一个相对递减的概率
可以简单分析下，当 random() 的值小于 0xFFFF 的 1/4,才会 level + 1，就意味着当有 1 - 1/4也就是3/4的概率是直接跳出，所以一层的概率是3/4,也就是 1-P，二层的概率是 P*(1-P),三层的概率是 P² * (1-P) 依次递推。