Skip to content

Latest commit

 

History

History
309 lines (220 loc) · 12.5 KB

2019-07-12.md

File metadata and controls

309 lines (220 loc) · 12.5 KB

一. Algorithm

做了 62. Unique Paths,一个网格,从左上角走到右下角的的路径数量,要求每次只能向右或者向下移动。一道动态规划的题目,

  • 动态规划的当前步骤都是之前步骤的叠加,那么走到某一个格子的路径数量,等于走到其上面格子的路径数量 + 走到左边格子的路径数量。图例如下,每个格子中的数字表示路径数:

x,y 表示格子的位置,那么可以得到公式:

pathNum[x, y] = pathNum[x, y - 1] + pathNum[x - 1, y]

那么通过递归就可以求得到达右下角的路径数量,代码如下:

class Solution {
     private int result = 0;

    private int m = 0;
    private int n = 0;

    public int uniquePaths(int m, int n) {

        this.m = m;
        this.n = n;

        if (m == 0 || n == 0) {
            return 0;
        }
        if (m == 1 || n == 1) {
            return 1;
        }

        return helper(m - 1, n) + helper(m, n - 1);
    }

    private int helper(int x, int y) {

        if (x == 1 && y == 1) {
            return 1;
        }
        
        if (x < 1 || y < 1) {
            return 0;
        }
        return helper(x - 1, y) + helper(x, y - 1);
    }
}

上面的代码虽然可以解决问题,但是在实际提交时会超时,因为会存在重复计算的问题,如图中所示,方框中的两个格子都会计算到圆圈中的格子,这样导致很多位置都要递归计算多次。解决思路是空间换时间,新建一个二维数组,记录已经计算过的格子的路径数,如果已经计算过了直接取值,不在做重复计算,改善后的代码如下:

class Solution {

    private int grid[][];


    public int uniquePaths(int m, int n) {

        if (m == 0 || n == 0) {
            return 0;
        }
        if (m == 1 || n == 1) {
            return 1;
        }

        this.grid = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                this.grid[i][j] = 0;
            }
        }
        return helper(n - 2, m-1) + helper(n-1, m - 2);
    }

    private int helper(int x, int y) {

        if (x == 0 && y == 0) {
            return 1;
        }

        if (x < 0 || y < 0) {
            return 0;
        }

        if (grid[x][y] == 0) {
            int sum = helper(x - 1, y) + helper(x, y - 1);
            grid[x][y] = sum;
        }
        return grid[x][y];
    }
}

实际提交运行时间接近 0ms。时间复杂度为 O(N),空间复杂度为 O(N)。

类似的还有第 64. Minimum Path Sum,这里是求和,本质上和 62 题的解题思路一致,从每到一个位置的最小和等于其上一个或者左边最小和加上本身的值,同时为了防止重复计算,空间换时间,记录下每次走过的位置处的和,代码如下:

class Solution {
    private int grid[][];
    private int sumGrid[][];

    public int minPathSum(int[][] grid) {
        int row = grid.length;
        int col = grid[0].length;
        this.grid = grid;
        this.sumGrid = new int[row][col];

        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                sumGrid[i][j] = -1;
            }
        }

        return helper(row - 1, col - 1);
    }

    private int helper(int x, int y) {

        if (sumGrid[x][y] == -1) {
            if (x == 0 && y == 0) {
                sumGrid[x][y] = this.grid[x][y];
            } else if (x == 0) {
                sumGrid[x][y] = this.grid[x][y] + helper(x, y - 1);
            } else if (y == 0) {
                sumGrid[x][y] = this.grid[x][y] + helper(x - 1, y);
            } else {
                sumGrid[x][y] = this.grid[x][y] + Math.min(helper(x - 1, y), helper(x, y - 1));
            }

        }
        return sumGrid[x][y];
    }
}

二. Review

读了 “Hot-Warm” Architecture in Elasticsearch 5.x 这篇文章。主要介绍了 ES 的冷热架构的相关操作。下面的简单的概要:

当用 ES 管理的数据其时间跨度非常大时,ES 官方建议我们采用 "Hot-Warm" architecture,将 ES 的节点分为三类:

  • Master 主节点
  • Hot-Node
  • Warm-Node

下面是三种节点的简单介绍:

1. 主节点

为了保证集群的高可用,建议每个集群至少有三个 Master 主节点,另外为了避免脑裂问题,此时应该将 discovery.zen.minimum_master_nodes 设置为 2。主节点只负责集群的管理以提高系统整体的稳定性。因为其不需要负责数据的存储以及查询、索引等操作,这样避免了因为数据存储、索引、检索导致的内存磁盘占用、垃圾回收等问题,因此其对 CPU、内存、磁盘的要求都相对较低。

2. Hot nodes

Hot nodes 是一种专门的数据节点,包含了集群中所有的索引,其存储的最新创建的索引数据,因为这些数据通常是被最频繁访问的数据。因为 ES 倒排索引的构建和数据检索是非常消耗 CPU、内存资源的,因此 Hot nodes 应该配置较高的 CPU 和内存,并且底层存储应该使用 SSD 硬盘。官网建议至少有三个 Hot nodes 以保证可用性,但具体的节点集群搭建还要取决于实际的场景。

3. Warm nodes

Warm nodes 也是一种数据节点,其存放大量的、只读的并且不被经常访问的数据。因为只读,因此 Warm nodes 可以使用普通硬盘,和 Hot nodes 一样,官网也建议至少配置三个 Warm nodes。当然因为 Warm nodes 的数据量较大,可能还会需要更多的节点来满足性能要求,同时建议 Warm nodes 拥有和 Hot nodes 相同的 CPU 和 内存配置,具体怎样可以根据实际的测试来决定。

上面是对三种节点的简单介绍,简单说就是 Master 主节点只管集群管理,不负责数据存储、索引和检索。Hot nodes 负责最新的、最频繁访问数据的存储,配置最高的 CPU、内存和 SSD 硬盘。而 Warm nodes 则负责历史数据的存储,只读不写,除了可以使用普通机械硬盘外其他配置尽量与 Hot nodes 相同。

知道了节点的含义,接下来就是如何配置相关节点了,对于 ES 节点类型,其主要就是主节点和数据节点,而数据节点可以通过 node.attr.box_type 字段来设置标注自己属于哪种节点,对于上面提到的两种节点,可以通过如下方式配置:

Hot nodes

ES 配置文件配置:node.attr.box_type: hot
启动命令行配置:./bin/elasticsearch -Enode.attr.box_type=hot

Warm nodes

ES 配置文件配置:node.attr.box_type: warm
启动命令行配置:./bin/elasticsearch -Enode.attr.box_type=warm

box_type 字段可以视作一个标签,你可以根据需要随意赋值,ES 通过该字段将索引进行归类存储,比如通过如下配置我们可以将索引存储到 Hot nodes:

PUT /logs_2016-12-26
{
  "settings": {
    "index.routing.allocation.require.box_type": "hot"
  }
}

等过一段时间后这些日志数据就变成旧数据被访问的不那么频繁了,此时可以将索引设置改为 Warm nodes,如下:

PUT /logs_2016-12-26/_settings 
{ 
  "settings": { 
    "index.routing.allocation.require.box_type": "warm"
  } 
}

我们可以通过 Index Template 索引模板来预先配置,将某些类型新建的索引都存储到 Hot nodes 上,比如:

{
  "template" : "indexname-*",
  "version" : 50001,
  "settings" : {
             "index.routing.allocation.require.box_type": "hot"
 ...

当然也可以通过通配符将所有新建索引加到 Hot nodes 上,如下:

{
  "template" : "*",
  "version" : 50001,
  "settings" : {
           "index.routing.allocation.require.box_type": "hot"
 ...

然后等你确认某些索引已经不会被修改且频繁查询了,那么可以将其移动到 Warm 节点上。

另外在 Warm 节点上,我们可以在配置文件中设置 index.codec: best_compression 。然后当数据存储到 Warm 节点上时,我们可以调用 _forcemerge API 来实现 Segment 的合并,这样操作不仅可以节省内存和磁盘空间,并且还会通过设置的 best_compression 来重写索引。

以上就是手动操作构建冷热架构的简单介绍,另外还可以通过 Curator 来自动操作,比如下面的配置,其可以在三天后自动将新建的 Hot 节点上的索引重新分配到 Warm 节点上, Curator 自己没用过,这里不做赘述了,有兴趣的同学可以参考其文档。

actions:
  1:
    action: allocation
    description: "Apply shard allocation filtering rules to the specified indices"
    options:
      key: box_type
      value: warm
      allocation_type: require
      wait_for_completion: true
      timeout_override:
      continue_if_exception: false
      disable_action: false
    filters:
    - filtertype: pattern
      kind: prefix
      value: logstash-
    - filtertype: age
      source: name
      direction: older
      timestring: '%Y.%m.%d'
      unit: days
      unit_count: 3

三. Tips

之前将博客从 Hexo 迁移到了 Hugo,重新整理了一下,本周将域名配置了一下,简要梳理下步骤,希望对有需要的小伙伴有帮助,更详细的文档参考这里 Using a custom domain with GitHub Pages。下面是我自己的操作步骤。

1. 申请域名

可以在 Godaddy、阿里云等网站自行申请域名,我自己的域名是在阿里云申请的 www.zouyingjie.com。

2. 设置 CNAME

在本地博客目录下的 static 目录下创建 CNAME 文件,将我们申请的域名填进去,这样每次部署更新博客时都会将 CNAME 文件更新上去。

3. 设置 DNS 解析

域名申请完了还需要设置 DNS 解析,使其解析到我们的 Github 仓库主页,可以分别通过配置 A IP 类型和 CNAME 域名类型来设置解析。我这里用的是 CNAME 类型,将自己申请的域名解析到 Github 默认提供的仓库域名上。

4. Github 仓库设置自定义域名

Github 默认给我们的仓库提供了默认的域名,比如我的博客仓库默认域名为 zouyingjie.github.io。除此之外我们可以在仓库的 Settings -> Options -> GitHub Pages -> Custom domain 中自行设置域名。如图:

将自定义的域名填进去即可。

完成后等 DNS 解析生效就可以通过域名来访问我们的博客啦。

另外记一个 crontab 遇到的问题,在一台新的服务器上加定时任务时,执行 crontab -e 命令出现了下面报错并且无法编辑文件:

no crontab for username - using an empty one 888

原因是没有指定 EDITOR 环境变量,默认使用了 ed 编辑器,按 Q 可以退出,然后将设置 EDITOR 环境变量为某一款你用的编辑器就可以修复该问题了,如下:

export EDITOR=vim
or
export EDITOR=emacs

设置完成后就可以正常执行 crontab -e 命令编辑定时任务文件了,当然更好的方式是加在 .zshrc 或者 .bashrc 配置文件中,这样就不用每次用的时候都要设置了。

四. Share

准备拿出一段时间学习以 TCP/IP 为主的网络协议了,分享本周整理的网络协议学习笔记,这是第一篇:【死磕网络协议(一) 】一次完整的 TCP 数据收发过程抓包分析