分类: 技术

  • ClickHouse 服务迁移记录

    ClickHouse 服务迁移记录

    旧的 ClickHouse 服务部署在 AWS EKS 集群中,与其他服务共享资源,现迁移到独立的 EC2 机器上,独享资源。考虑成本,新的机器使用 arm 处理器。

    使用到的工具是 clickhouse-backup

    备份原服务数据

    使用 clickhouse-backup 备份原服务数据到 S3,备份 example_schema schema 下的所有表到 S3 的命令如下:

    ./clickhouse-backup create_remote -c clickhouse-backup-config.yml -t [example_schema].*

    此操作会把 example_schema 下的表结构以及数据备份到 S3 的目录,S3 的相关配置在 clickhouse-backup-config.yml 里面。

    备份后会在 S3 生成以备份时间命名的文件夹:

    这个文件夹的名字作为备份的名字会在恢复数据的时候使用。

    在新服务上恢复数据

    使用原服务同样的 clickhouse-backup-config.yml 配置,把数据库相关配置换成新服务的:

    clickhouse:
      username: username
      password: "password"
      host: localhost
      port: 9000
      disk_mapping: {}
      skip_tables:
      - system.*
      timeout: 5m
      freeze_by_part: false
      secure: false
      skip_verify: false
      sync_replicated_tables: true
      skip_sync_replica_timeouts: true
      log_sql_queries: false

    之后,执行恢复数据的命令:

    ./clickhouse-backup restore_remote -c clickhouse-backup-config.yml 2023-03-24T01-46-40

    等待命令执行完毕,数据即可恢复成功。

    Clickhouse 安装

    使用 install.sh 安装脚本,代码如下,LATEST_VERSION 写死想要安装的版本,否则会根据服务器信息安装最新的版本。install.sh 脚本代码如下:

    LATEST_VERSION=22.3.6.5-arm64
    export LATEST_VERSION
    
    case $(uname -m) in
      x86_64) ARCH=amd64 ;;
      aarch64) ARCH=arm64 ;;
      *) echo "Unknown architecture $(uname -m)"; exit 1 ;;
    esac
    
    for PKG in clickhouse-common-static clickhouse-common-static-dbg clickhouse-server clickhouse-client clickhouse-keeper
    do
      curl -fO "https://packages.clickhouse.com/tgz/stable/$PKG-$LATEST_VERSION-${ARCH}.tgz" \
        || curl -fO "https://packages.clickhouse.com/tgz/stable/$PKG-$LATEST_VERSION.tgz"
    done
    
    tar -xzvf "clickhouse-common-static-$LATEST_VERSION-${ARCH}.tgz" \
      || tar -xzvf "clickhouse-common-static-$LATEST_VERSION.tgz"
    sudo "clickhouse-common-static-$LATEST_VERSION/install/doinst.sh"
    
    tar -xzvf "clickhouse-common-static-dbg-$LATEST_VERSION-${ARCH}.tgz" \
      || tar -xzvf "clickhouse-common-static-dbg-$LATEST_VERSION.tgz"
    sudo "clickhouse-common-static-dbg-$LATEST_VERSION/install/doinst.sh"
    
    tar -xzvf "clickhouse-server-$LATEST_VERSION-${ARCH}.tgz" \
      || tar -xzvf "clickhouse-server-$LATEST_VERSION.tgz"
    sudo "clickhouse-server-$LATEST_VERSION/install/doinst.sh" configure
    sudo /etc/init.d/clickhouse-server start
    
    tar -xzvf "clickhouse-client-$LATEST_VERSION-${ARCH}.tgz" \
      || tar -xzvf "clickhouse-client-$LATEST_VERSION.tgz"
    sudo "clickhouse-client-$LATEST_VERSION/install/doinst.sh"
  • 在 ubuntu 服务器上搭建 Gitlab 服务

    在 ubuntu 服务器上搭建 Gitlab 服务

    使用 omnibus 方式安装 gitlab 官方的 gitlab 服务,存在病毒,导致 CPU 跑满,改用 docker 方式安装。

    参考文档

    安装 docker: Install Docker Engine on Ubuntu

    安装 gitlab-jh: 极狐GitLab Docker 镜像

    安装过程

    1. 使用 Docker Engine 安装极狐GitLab:

    export GITLAB_HOME=/srv/gitlab
    
    docker run --detach \
      --hostname mygitlab.app \
      --publish 443:443 --publish 80:80 --publish 24:22 \
      --name gitlab \
      --restart always \
      --volume $GITLAB_HOME/config:/etc/gitlab \
      --volume $GITLAB_HOME/logs:/var/log/gitlab \
      --volume $GITLAB_HOME/data:/var/opt/gitlab \
      --shm-size 256m \
      registry.gitlab.cn/omnibus/gitlab-jh:latest

    以上代码执行,初始化 gitlab-jh,耗时较长。

    2. 安装 gitlab-runner: Run GitLab Runner in a container

    docker run -d --name gitlab-runner --restart always \
      -v /srv/gitlab-runner/config:/etc/gitlab-runner \
      -v /var/run/docker.sock:/var/run/docker.sock 
      gitlab/gitlab-runner:latest -m 

    3. 注册 docker runner,执行过程中会有交互式问答,记录如下:

    docker run --rm -it -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register
    
    http://mygitlab.app/
    
    Runner token:xxxxxxxxxxxx_xxx
    
    alpine:lastest
    
    alpine:lastest
    
    docker
    
    alpine:lastest
    
    Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"

    4. 修改 docker runner 的配置,添加卷:Advanced configuration

    nano /srv/gitlab-runner/config/config.toml
    
    volumes = ["/home/www/mygitlab.app:/build/mygitlab.app:rw"]

    其他记录

    重启服务,查看日志相关命令:

    docker restart gitlab-runner
    
    docker logs gitlab-runner -f

    进入 Docker 容器执行命令:

    docker exec -it xxxxxxxxxxx sh

    其他参考文档

    六. GitLab-CI/CD-实战:前端Vue项目

    极狐GitLab CI/CD如何在docker in docker 模式下将流水线的产物存储到宿主机上?

    在 Ubuntu 上开启 Swap

    dd if=/dev/zero of=/swapfile count=8096 bs=1M

    https://blog.csdn.net/qq_43557686/article/details/126028541

    https://blog.csdn.net/huyongfu2004/article/details/122710325

  • Amazon Sagemaker 常用方法

    Amazon Sagemaker 常用方法

    从 S3 下载文件:

    import boto3
    s3 = boto3.resource('s3')
    
    s3.meta.client.download_file('bucket', 'origin_object_key', 'local_path')

    往 S3 上传文件:

    s3.meta.client.upload_file('local_path', 'bucket', 'origin_object_key')

    python zip 解压

    import zipfile
    with zipfile.ZipFile(path_to_zip_file, 'r') as zip_ref:
        zip_ref.extractall(directory_to_extract_to)
  • Pandas DataFrame 常用操作

    Pandas DataFrame 常用操作

    Pandas 读取 Excel 或者 csv 文件:

    import pandas as pd
    
    pd.read_excel(r'filepath', sheet_name='sheet1')
    pd.read_csv(r'filepath')

    DataFrame 拼接:

    df1.append(df2)

    DataFrame 筛选:

    df.loc[df['A'] == 1],多条件 df.loc[(df['A'] == 1) & (df['B'] == 2)]

    DataFrame 分组:

    df.groupby(by=['A','B','C'])

    DataFrame 删除列:

    df.drop(labels=['A', 'B'], axis=1)

    DataFrame 去重:

    df.drop_duplicates(subset=['A','B','C','D'])

    从 DataFrame 的头部或者尾部截取数据输出:

    df.head()
    df.tail(3)

    不要在 for 循环中 append 数据,效率非常低

    Pandas 的数据不能直接修改,添加属性需要复制一份:

    import copy
    
    new_frame_data = copy.copy(old_frame_data)
    new_frame_data.attr = value
  • 为 AWS Python Lambda 函数添加层,支持使用 pandas 库

    为 AWS Python Lambda 函数添加层,支持使用 pandas 库

    参考:

    1. 创建和共享 Lambda 层
    2. How to use pandas in AWS Lambda

    步骤1~4需要在 AWS 的 EC2 实例上进行,以便使用相同的依赖环境,这里使用 AWS 的 Sagemaker 进行。

    步骤1:

    找到需要添加层的 Lambda 函数的 Python 版本,在 AWS EC2 环境创建层时需要使用相同的版本,创建 AWS Lambda 函数时能看到,可选用的 Python Lambda 函数版本为 3.9,EC2 环境使用 Python 3.9.13 版本。

    conda create -n aws_lambda_env python==3.9.13conda activate aws_lambda_env

    在 Sagemaker 环境下,激活 conda 虚拟环境需要使用:

    source activate aws_lambda_env

    步骤2:

    创建一个文件夹

    mkdir python

    步骤3:

    安装需要添加的库到这个环境中

    pip install openpyxl pandas -t python

    步骤4:

    打包目录

    zip -r pandas.zip python

    步骤5:

    在 AWS 控制台创建层,然后上传打包好的层 pandas.zip

    在 AWS 控制台上传打包好的层

    步骤6:

    创建一个新的 Lambda 函数,选择可以添加层的运行时环境,这里选择的是 Python 3.9

    创建 Lambda 函数

    步骤7:

    为创建的 Lambda 函数添加层:

    添加一个 Layer

    选择刚刚创建的层

    选择刚刚创建的层

    最后写一段代码测试一下,是否能 import 成功新添加层的库。

    import logging
    
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    def lambda_handler(event, context):
         logger.info(pd.__version__)
  • 在 AWS Sagemaker 上使用 Paddlehub 搭建 OCR 文字识别服务,识别身份证、银行卡、票据等

    在 AWS Sagemaker 上使用 Paddlehub 搭建 OCR 文字识别服务,识别身份证、银行卡、票据等

    Paddlehub 是百度飞浆推出的预训练模型管理工具,Paddlehub 提供对预训练模型的安装、卸载、升级、部署等功能。

    Sagemaker 是 AWS 推出的机器学习工具平台,集成了 Jupyter Notebook,通过 Sagemaker WEB 工具或者 SDK 可快速完成机器学习相关的模型训练、部署等过程。

    在 Sagemaker 上面部署自己的机器学习模型分为以下几个步骤:

    1. 将服务封装成成符合 Sagemaker 要求的镜像托管到 AWS ECR 容器仓库;
    2. 在 Sagemaker 上将镜像制作成 Sagemaker 定义的模型;
    3. 创建终端节点配置,部署终端节点。

    其中,第一步是比较繁琐的,要按照 Sagemaker 平台的要求来合理规划代码及数据在 Docker 镜像中的路径。2、3步都可以通过 Sagemaker 的 WEB 界面完成。

    将服务封装成成符合 Sagemaker 要求的镜像托管到 AWS ECR 容器仓库

    Sagemaker 约定了推理模型的容器必须实施在端口 8080 上响应 /invocations 和 /ping 的 Web 服务器。也就是说推理模型必须部署一个端口为 8080 的 Web 服务,这个服务需要有两个 API:一个是 http://localhost:8080/invocations 用来接收数据,返回推理结果;一个是 http://localhost:8080/ping 用来响应健康检查。

    Paddlehub 提供了启动推理服务的快速命令:

    hub serving start -m chinese_ocr_db_crnn_mobile -p 8866

    其中,-p 参数用来指定端口,这里保持使用 8866 端口号。计划在镜像里面加一个 Nginx 的 server 配置,用来响应 8080 端口上 path 为/invocations 的推理请求,将请求转发到 Paddlehub 服务 的 8866 端口上,并添加另外一个 server 配置,响应 8080 端口上 path 为 /ping 的请求,来通过 Sagemaker 的健康检查。

    网络拓扑图

    基础镜像基于 ubuntu docker 基础镜像制作。

    docker run -it ubuntu /bin/bash
    apt-get update

    安装 nginx 并配置

    apt-get install nginx

    将准备好的 Nginx 配置文件上传到 docker 镜像中:

    docker cp /home/ec2-user/SageMaker/ocr/ocr.conf [启动的容器ID]:/etc/nginx/conf.d/ocr.conf

    Nginx 配置参考:

    server {
        listen 8080;
        server_name localhost;
    
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X_Forward_For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    
        location /ping {
          add_header Access-Control-Allow-Origin * always;
          add_header Access-Control-Allow-Headers X-Requested-With;
          add_header Access-Control-Allow-Methods PUT,POST,GET,DELETE,OPTIONS;
          
          return 200 "OK";
        }
    
        location /invocations {
          add_header Access-Control-Allow-Origin * always;
          add_header Access-Control-Allow-Headers X-Requested-With;
          add_header Access-Control-Allow-Methods PUT,POST,GET,DELETE,OPTIONS;
          
          proxy_pass http://127.0.0.1:8866/predict/chinese_ocr_db_crnn_server;
        }
    }
    

    安装 python 3.6,移除默认的 python 3.8

    ubuntu 镜像中可能存在 python3.8 版本, 在我安装的时候,Paddlehub 在 python3.8 下运行会出现问题。

    apt-get install software-properties-common
    add-apt-repository ppa:deadsnakes/ppa
    apt-get update
    apt-get install python3.6
    apt-get purge remove python3.8
    echo alias python=python3 >> ~/.bashrc
    echo alias python3=python3.6 >> ~/.bashrc
    source ~/.bashrc

    安装 PaddlePaddle、PaddleHub

    apt-get install python3-pip
    echo alias pip=pip3>> ~/.bashrc
    source ~/.bashrc
    
    pip install paddlepaddle-gpu==2.0.0rc
    pip install paddlehub==2.0.0b

    安装 OCR 文字识别模型

    apt-get install git libgl1-mesa-glx
    pip install sentencepiece  shapely pyclipper
    hub install chinese_ocr_db_crnn_mobile==1.1.1

    将准备好的启动脚本上传到正在编辑的容器中

    这里有两个,一个是供 CPU 实例类型用的,一个是供 GPU 实例类型用的。

    docker cp /home/ec2-user/SageMaker/ocr/run-gpu.sh [启动的容器ID]:/root/run.sh # 或者
    docker cp /home/ec2-user/SageMaker/ocr/run.sh [启动的容器ID]:/root/run.sh

    run 脚本参考:

    #!/bin/bash
    
    # nginx
    /usr/sbin/nginx > /var/log/nginx.log 2>&1 &
    
    # ocr service
    hub serving start -m chinese_ocr_db_crnn_server --use_gpu > /var/log/chinese_ocr_db_crnn_server.log 2>&1 & # For gpu
    # hub serving start -m chinese_ocr_db_crnn_server > /var/log/chinese_ocr_db_crnn_server.log 2>&1 & # For cpu
    
    
    # just keep this script running
    while [[ true ]]; do
        sleep 1
    done

    打包上传基础镜像到 AWS ECR

    docker commit -m "[note]" -a "19050023" [启动的容器ID] [镜像名称]:[base-version]
    docker tag [镜像名称]:[base-version] [AWS ECR 地址].amazonaws.com.cn/[镜像名称]:[base-version]
    docker push [AWS ECR 地址].amazonaws.com.cn/[镜像名称]:[base-version]

    Dockerfile 配置参考:

    # FROM [镜像名称]:v1
    FROM [镜像名称]:v1-gpu
    
    # COPY run.sh /root/run.sh # For cpu
    COPY run-gpu.sh /root/run.sh # For gpu
    
    RUN chmod +x /root/run.sh
    
    ENTRYPOINT ["/root/run.sh"]
    
    EXPOSE 8080

    在 Sagemaker 上将镜像制作成 Sagemaker 定义的模型

    创建模型

    选择“创建模型”,进入创建模型的配置页面

    配置容器

    推理代码图像位置一栏填上面步骤中上传的推理镜像的 ECR 地址,模型构件指预训练模型等,可以上传到 s3 存储,SageMaker 运行的时候会拉去模型数据,解压到指定的目录中,这里不需要。完成配置,点“创建模型”即可。

    创建终端节点,部署终端节点

    在 SageMaker 终端节点模块选择创建终端节点

    创建新的终端节点配置,添加模型,并调整实例类型

    点击 创建终端节点配置 即创建好了配置文件。

    最后,点击页面底部的“创建终端节点”,服务就部署好了。

  • “整数反转”问题的 Javascript 解答

    “整数反转”问题的 Javascript 解答

    题目

    给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

    如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。

    假设环境不允许存储 64 位整数(有符号或无符号)。
     

    示例 1:

    输入:x = 123
    输出:321


    示例 2:

    输入:x = -123
    输出:-321


    示例 3:

    输入:x = 120
    输出:21


    示例 4:

    输入:x = 0
    输出:0

    提示:

    -231 <= x <= 231 - 1

    分析

    将数当作字符串,反向遍历就能解决问题了,需要注意两个点:

    1. 负数的处理;
    2. 超出 32 位数范围,直接返回 0。

    解答

    /**
     * @param {number} x
     * @return {number}
     */
    var reverse = function (x) {
      var isNegativeNumber = false;
      if (x < 0) {
        isNegativeNumber = true;
      }
      var xStr = x.toString();
      var reverseXArr = [];
    
      for (var i = xStr.length - 1; i >= 0; i--) {
        reverseXArr.push(xStr[i]);
      }
    
      var result = parseInt(reverseXArr.join(""), 10);
    
      if (isNegativeNumber) {
        result = 0 - result;
      }
    
      if (result < -Math.pow(2, 31) || result > Math.pow(2, 31) - 1) {
        result = 0;
      }
    
      return result;
    };

    题目来源:力扣(LeetCode)

    链接:https://leetcode-cn.com/problems/reverse-integer

  • “Z 字形变换”问题的 Javascript 解答

    “Z 字形变换”问题的 Javascript 解答

    题目:

    将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。

    比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:

    P   A   H   N
    A P L S I I G
    Y   I   R
    之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
    请你实现这个将字符串进行指定行数变换的函数:
    
    string convert(string s, int numRows);

    示例 1:

    输入:s = "PAYPALISHIRING", numRows = 3
    输出:"PAHNAPLSIIGYIR"

    示例 2:

    输入:s = "PAYPALISHIRING", numRows = 4
    输出:"PINALSIGYAHRPI"
    解释:
    P     I    N
    A   L S  I G
    Y A   H R
    P     I

    示例3:

    输入:s = "A", numRows = 1
    输出:"A"

    思考:

    先找规律看看

    0123456
    0P[1]A[5]H[9]N[13]
    1A[2]P[4]L[6]S[8]I[10]I[12]G[14]
    2Y[3]I[7]R[11]
    示例1,numRows = 3
    0123456
    0P[0]I[6]N[12]
    1A[1]L[5]S[7]I[11]G[13]
    2Y[2]A[4]H[8]R[10]
    3P[3]I[9]
    示例2,numRows = 4

    设字符的下标为 n

    梯度(t) = numRows + (numRows -2),以示例2为例,梯度为 4 + (4 – 2) = 6

    计算列数(x) :

    nt = Math.floor(n / t) 得到字符 n 在第 nt 个梯度内(从 0 开始计数),每个梯度包含 tx 列,tx = numRows – 1

    n % t 将所有字符放在第一个梯度内考虑,有两种情况 :

    n % t < numRows 时,x 等于 0 + s[n] 所在的梯度乘以梯的长度,即: x = 0 + nt x tx,整理后是 x = nt x tx

    n % t >= numRows 时,也就是 s[n] 是在 “Z“ 字的折线位置的,放到第一个梯度里面看,x 应该就是 n % numRows 的值,属于其他梯度内的数,加上梯度即可,考虑到下标是从 0 开始的,所以 x = (((n % t) + 1) % numRows) + nt x tx;

    计算行数(y)

    行数的计算和列数的计算思路一样,把所有字符放到第一个梯度内考虑,而且不需要再加上梯度了。同样分两种情况:

    n % t < numRows 时,n % t 将 s[n] 放到第一个梯度内考虑,行数 y = n % numRows ,考虑梯度,最终结果为:y = n % t % numRows。

    n % t >= numRows 时,也就是计算 “Z” 字折线上的数字。n % t 将 s[n] 放到第一个梯度内考虑,观察 s[n] 的下标与行数的关系,可以得到 s[n] 不可能出现在第一行和最后一行,第一个梯度内,n + 1 – numRows 可得到 s[n] 到表格底部的距离,numRows – 1 再减去这个距离,就得到行数 y 了,因此结果为:y = numRows – 1 – ((n % t) + 1 – numRows)。

    还有一种情况是 numRows === 1 的时候,实际上把原字符串直接输出就是了。

    解答

    /**
     * @param {string} s
     * @param {number} numRows
     * @return {string}
     */
    var convert = function (s, numRows) {
      var matrix = [];
    
      for (var y = 0; y < numRows; y++) {
        // 构建行
        matrix[y] = [];
      }
    
      // 梯度
      var t = numRows + numRows - 2;
    
      for (var n = 0; n < s.length; n++) {
        // 测试列的判断逻辑是否正确
    
        // 初始化列位置
        var x = 0;
    
        // 初始化行位置
        var y = 0;
    
        // 得到元素在哪个梯度内
        var nt = Math.floor(n / t);
    
        // 每个梯度占据的列数
        var tx = numRows - 1;
    
        if (n % t < numRows) {
          x = 0 + nt * tx;
          y = (n % t) % numRows;
        } else {
          x = (((n % t) + 1) % numRows) + nt * tx;
          y = numRows - 1 - ((n % t) + 1 - numRows);
        }
    
        matrix[y][x] = s[n];
      }
    
      var result = "";
    
      matrix.forEach(function (element) {
        element.forEach(function (item) {
          if (item) result = result.padEnd(result.length + 1, item);
        });
      });
    
      return result;
    };
    

    题目来源:力扣(LeetCode)

    链接:https://leetcode-cn.com/problems/zigzag-conversion/

  • “最长回文子串”问题的 Javascript 解答

    “最长回文子串”问题的 Javascript 解答

    题目

    给你一个字符串 s,找到 s 中最长的回文子串。

    示例 1:

    输入:s = "babad"
    输出:"bab"
    解释:"aba" 同样是符合题意的答案。

    示例 2:

    输入:s = "cbbd"
    输出:"bb"

    示例 3:

    输入:s = "a"
    输出:"a"

    示例 4:

    输入:s = "ac"
    输出:"a"

    提示:

    1 <= s.length <= 1000
    s 仅由数字和英文字母(大写和/或小写)组成

    解题思路:

    先找相同字符形成的最长子串(A),如:‘aa’、‘aaa’、‘aaaa’;

    然后判断 A 的前面和后面距离与 A 距离相同的字符是否一样,如果一样,继续查找,直到不一样为止,就找到了一个回文字符串;

    遍历字符串,找到所有回文字符串,取出最长的一个。

    解答:

    var findPalindrome = function (s, i) {
      var j = 1;
    
      while (i + j < s.length && s[i] === s[i + j]) {
        j++;
      }
    
      var n = 0;
      while (
        i - n >= 0 &&
        i + (j - 1) + n < s.length &&
        s[i - n] === s[i + (j - 1) + n]
      ) {
        n = n + 1;
      }
    
      return s.substring(i - (n - 1), i + (j - 1) + n);
    };
    
    /**
     * @param {string} s
     * @return {string}
     */
    var longestPalindrome = function (s) {
      var result = "";
      for (var i = 0; i < s.length; i++) {
        var resulti = findPalindrome(s, i) || "";
    
        if (resulti.length > result.length) {
          result = resulti;
        }
      }
    
      return result;
    };
    

    题目来源:力扣(LeetCode)

    链接:https://leetcode-cn.com/problems/longest-palindromic-substring/

  • “寻找两个正序数组的中位数”问题的 Javascript 解答

    “寻找两个正序数组的中位数”问题的 Javascript 解答

    题目:

    给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

    示例 1:

    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

    示例 2:

    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

    示例 3:

    输入:nums1 = [0,0], nums2 = [0,0]
    输出:0.00000

    示例 4:

    输入:nums1 = [], nums2 = [1]
    输出:1.00000

    示例 5:

    输入:nums1 = [2], nums2 = []
    输出:2.00000

    提示:

    nums1.length == m
    nums2.length == n
    0 <= m <= 1000
    0 <= n <= 1000
    1 <= m + n <= 2000
    -106 <= nums1[i], nums2[i] <= 106

    进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

    解题思路:

    将两个数组合并成一个数组后排序。

    如果合并后的数组的长度是偶数,取中间两个数的平均数;如果合并后的数组的长度是奇数,取中间的数。

    解答:

    var sortNumber = function (a, b) {
      return a - b;
    };
    
    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number}
     */
    var findMedianSortedArrays = function (nums1, nums2) {
      var nums = nums1.concat(nums2).sort(sortNumber);
      var isEvenNum = nums.length % 2 === 0;
      var result = 0;
      var len = nums.length;
    
      if (isEvenNum) {
        result = (nums[len / 2 - 1] + nums[len / 2]) / 2;
      } else {
        result = nums[(len - 1) / 2];
      }
    
      return result;
    };

    题目来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays

  • 命令式编程、声明式编程、响应式编程与 RxJS

    命令式编程、声明式编程、响应式编程与 RxJS

    命令式编程、声明式编程、响应式编程

    命令式编程(Imperative):详细的命令机器怎么(How)去处理一件事情以达到你想要的结果(What);
    声明式编程(Declarative):只告诉你想要的结果(What),机器自己摸索过程(How)。

    出自:《命令式编程(Imperative) vs 声明式编程(Declarative)》

    命令式编程是我们一步一步告诉机器需要怎么做,机器按部就班地执行命令。声明式编程是我们告诉机器我想要这样的结果,而不管他是怎么实现的,这更符合人类的思维。举一个数据过滤的例子来说明这一点,比如我们要打印下数组中存不存在 3。

    示例1:

    // 命令式编程做法
    let res = false;
    for(i = 0; i < dataArr.length; i++) {
        if (i === 3) {
            res = true;
        }
    }
    console.log(res);
    
    // 声明式编程做法
    let res = dataArr.filter(i => i === 3);
    console.log(res);
    

    Angular 中的 RxJS,用了“声明式编程范式”,是一个使用可观察对象实践“响应式编程”模型的库。那什么是响应式编程呢?

    它希望有某种方式能够构建关系,而不是执行某种赋值命令。
    响应式编程是一种通过异步和数据流来构建事务关系的编程模型。

    出自:《重新理解响应式编程》

    构建关系是指我们可以定义两个变量(A 和 B)之间具有某种永恒的关系。一旦 A 变量改变,我们不需要人为地对 B 变量进行任何处理,B 变量自动更改以满足与 A 变量已经定义好的关系。

    示例2:

    A = 1;
    
    B - A := 2;     // 定义一种关系,这里是指 B 减去 A 永远等于 2
    console.log(B); // B = 3
    
    A = 3;          // A 改变
    console.log(B); // B = 5
    

    数据流是响应式编程传递变化的值的方式,通过数据流将变化的数据不断向下传递。因此,我们可以链式的对数据处理。

    示意图1:
    data-flo
    上图与命令式编程的不同之处是:命令式编程我们会一步步的去实现花括号里面的操作,不是“我要颜色深一点”,而是“我让颜色深一点”。

    可以说响应式编程是声明式编程的一种,两者到底是从属关系还是其他关系,并不十分清楚,这似乎并不重要。个人倾向于把响应式编程当做一种更具体的声明式编程,是声明式编程的更高级范式。

    RxJS

    RxJS 是 JS 版本的 ReactiveX 库,ReactiveX 还有 JAVA、.NET、Swift 等语言的版本。无意造轮子,此篇仅对 RxJS 做一个标记式的记录和学习指引,详细了解可查阅:《RxJS 官网文档》(英文)以及《RxJS 中文文档》

    RxJS 是使用 Observables 的响应式编程的库,它提供了一系列 API 来支持响应式编程。

    学习 RxJS 有几个概念和注意点需要重点知晓:

    • Observable(可观察对象):RxJS 处理的数据或者是 RxJS 抽象操作或者数据的方式,它是函数的泛化,支持返回多个值,惰性运算,需要被订阅之后才会执行。使用 RxJS,就是在构造 Observable 和处理 Observable。
    • Subscription (订阅):观察者对象,或者说是订阅 Observable 的那个对象,更进一步可以说是接收 Observable 返回值的那个对象。
    • Subject (主体):可以理解为支持多播的 Observable。Subjects 是将任意 Observable 执行共享给多个观察者的唯一方式。
    • RxJS 的操作符:提供便捷的数据(事件流)处理,比如 map、filter,以及 combineAll、forkJoin、pairwise 等。
    • Rx.Observable.create 是 Observable 构造方法的别名,它通知是下面这个样子:
      Rx.Observable.create({
          next: Function,
          error: Function,
          complete: Function
      });
    

    RxJS 还提供了 from of 等其他创建 Observable 的方法。

    • Observables 并不是异步编程,传递值可以是同步的,也可以是异步的。
    • 在 Observable 执行中, 可能会发送零个到无穷多个 “Next” 通知。如果发送的是 “Error” 或 “Complete” 通知的话,那么之后不会再发送任何通知了。
  • openresty 部署 https 并开启 http2 支持

    openresty 部署 https 并开启 http2 支持

    这里分两步介绍,第一部是配置 https , 第二步是开启 http2 支持。事实上开启 http2 支持,必须配置站点使用 https 传输协议。

    HTTPS

    为站点部署开启 HTTPS 支持,需要一个可信任的第三方 SSL 证书,然后针对不同的服务器环境进行配置。

    我选择使用Certbot来部署一个免费、可自动更新的由Let’s Encrypt提供的期限为 90 天的权威可信任证书。Certbot官网根据不同的服务器环境提供了不同的部署方法,我使用 ubuntu + openresty(nginx) ,根据官网介绍部署方式如下:

    1. 安装 certbot(或者通过源码安装certbot
    $ sudo apt-get update
    $ sudo apt-get install python-software-properties software-properties-common
    $ sudo add-apt-repository ppa:certbot/certbot
    $ sudo apt-get update
    $ sudo apt-get install certbot
    
    1. 获得证书
    $ certbot(or path/certbot-auto) certonly --webroot -w /path/your-web-root -d your-domain.com -d www.your-domain.com
    

    执行上述代码后,会在系统的/etc/letsencrypt/live目录下生成以your-domain.com的目录,里面会生成cert.pem``chain.pem``fullchain.pem``privkey.pem``README几个文件接下来在 nginx 配置文件中需要用到fullchain.pem``privkey.pem两个文件,这有关SSL证书链的概念,超出本文叙述范围。
    3. 配置 nginx,在 server 中与 https 相关的配置如下

    server {
       ...
       listen 443 ssl;
       ssl on;
       ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
       ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    
       ssl_session_cache shared:le_nginx_SSL:1m;
       ssl_session_timeout 1440m;
    
       ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
       ssl_ciphers RC4:HIGH:!aNULL:!MD5;
       ssl_prefer_server_ciphers on;
       ...
    }
    
    1. 执行nginx -t测试 nginx 配置是否正确,确认无误后,重启 nginx 服务。
    2. 设置证书自动更新。
      上文说过Let’s Encrypt提供的免费证书期限为 90 天,certbot 提供了证书自动更新服务,通过源码安装则需要进行一些配置(配置系统的定时任务)。
      1. (Ubuntu) 在/var/spool/cron/crontabs/root文件中添加以下配置:
      0 0 * * * root /path/certbot-auto renew --quiet --no-self-upgrade
      1. 使用certbot renew --dry-run命令测试是否配置成功。

    HTTP2

    配置好 HTTPS 后开启 HTTP2 比较简单,两步即可完成。

    1. 重新编译openresty加入--with-http_v2_module --with-http_ssl_module选项,替换系统中使用的nginx执行文件:
    ./configure --with-http_v2_module --with-http_ssl_module
    make // 不要 make install
    

    关闭 nginx,使用编译生成的/resources/openresty-1.11.2.2/build/nginx-1.11.2/objs/nginx文件替换系统正使用的/usr/local/openresty/nginx/sbin/nginx文件,重启 nginx 即可。
    2. 修改 nginx 配置。在 listen 后添加 http2 ,如下所示:

    server {
       ...
       listen 443 ssl http2;
       ...
    }
    

    如果一开始就开启了 nginx 的 h2 模块,就不需要第一步了。至此,配置 https 并开启 http2 完成。