记一次物理机 OOM 的处理过程

1. 情景描述

1.1 技术背景

在之前开发的一个作业调度系统中,采用了一种最简单作业运行机制:

  • 物理机上启动称为 worker 的进程。

  • worker 进程与其他模块交互启动任务、停止任务、保存日志。

  • worker 运行作业的命令由调度模块生成派发。

  • 作业进程以 worker 进程的子进程的方式启动。

这种架构的优点是轻量级,任务的具体运行策略在运行时由调度中心决定,因此大量的特性可以在调度模块进行开发,worker 模块不需要频繁重启和更新。

但是缺点也同样明显,主要包括两个方面。

首先,因为物理机上没有做资源的隔离,所以所有作业共享物理机的资源,单个任务可能无限制地使用资源导致 worker 进程或者物理机的不稳定。

其次,作业都是由 worker 进程创建子进程的方式启动。一旦 worker 进程退出,原来作业进程的 ppid 都会变成 1。如果不对作业进程的信息进行持久化,那么重启之前的在运行的作业状态就会丢失,但是贸然重启又会导致同一个作业有两个实例同时运行。但即使持久化了作业进程的信息,在 worker 进程重启过程中运行结束的作业信息还是会丢失。

鉴于上述原因,针对当前的架构最重要的就是保证 worker 进程不会被意外终止。

1.2 问题描述

但是,就像墨菲定律所说:

如果事情有变坏的可能,不管这种可能性有多小,它总会发生。

最近频繁在半夜被报警电话打醒,worker 进程被 supervisor 重新拉起。只能先来一波令人窒息的运维操作,恢复这台物理机的状态,然后再排查问题的原因。

查看监控页面可知物理机的内存使用率和 CPU load 在短时间内一路走高,操作系统达到了 OOM 的边缘。

之前还发生过操作系统在类似的情况下直接重启的后果。

2. 排查手段

咨询了一下公司负责 SLA 的同事,初步判断时内核的 OOM-killer 将进程杀死导致了 worker 进程的重启。

2.1 OOM 的日志

首先 OOM-killer 发生作用时在内核的日志里是会有记录的:

  • /var/log/kern.log

  • /var/log/dmesg

    查找 Out of memory: Kill process 或者 Killed process 字样。

     sudo grep --color -n -B 30 'Killed process' /var/log/kern.log

    日志中会提示 OOM-killer 杀死进程前的内存使用信息、终止进程的 pid 以及 cmd 等。由于时间比较久远,相关日志被清除了,因此就不再这里贴出样例了。

2.2 OOM-killer 机制

根据下面几篇文章,可以简单了解一下 OOM-killer 机制的作用原理:

简单来说几个相关参数:

  • /proc/sys/vm/panic_on_oom

  • /proc/sys/vm/oom_dump_tasks

  • /proc/sys/vm/oom_kill_allocating_task

  • /proc/sys/vm/overcommit_memory

  • /proc/sys/vm/overcommit_ratio

  • /proc/[pid]/oom_score

  • /proc/[pid]/oom_adj

2.2.1 /proc/sys/vm/panic_on_oom

默认值是 0。

如果设置为 0,内核会杀死内存占用过多的进程。通常杀死内存占用最多的进程,系统就会恢复。

如果设置为 1,在发生 OOM 时,内核会 panic 。然而,如果一个进程通过内存策略或进程绑定限制了可以使用的节点,并且这些节点的内存已经耗尽,OOM-killer 可能会杀死一个进程来释放内存。在这种情况下,内核不会 panic,因为其他节点的内存可能还有空闲,这意味着整个系统的内存状况还没有处于崩溃状态。

如果设置为 2,在发生 OOM 时总是会强制 panic,即使在上面讨论的情况下也一样。即使在 memory cgroup 限制下发生的 OOM,整个系统也会 panic。

2.2.2 /proc/sys/vm/oom_dump_tasks

默认值是 1(启用),在内核执行OOM-killing时会打印系统内进程的信息(不包括内核线程),信息包括 pid、uid、tgid、vm size、rss、nr_ptes,swapents,oom_score_adj 和进程名称。

2.2.3 /proc/sys/vm/oom_kill_allocating_task

默认值是 0。如果设置为 0,OOM killer 会扫描进程列表,选择一个进程来杀死。通常都会选择消耗内存内存最多的进程,杀死这样的进程后可以释放大量的内存。如果设置为非零值,OOM killer 只会简单地将触发 OOM 的进程杀死,避免遍历进程列表(代价比较大)。如果 panic_on_oom 被设置,则会忽略 oom_kill_allocating_task 的值。

2.2.4 /proc/sys/vm/overcommit_memory

默认值为 0 。该参数有三个值,分别是:

  • 0:当用户空间请求更多的的内存时,内核尝试估算出剩余可用的内存。

  • 1:当设这个参数值为 1 时,内核允许超量使用内存直到用完为止。

  • 2:当设这个参数值为 2 时,内核会使用一个决不过量使用内存的算法,即系统整个内存地址空间不能超过 swap + 50% 的 RAM 值,50% 参数的设定是在 overcommit_ratio 中设定。

2.2.5 /proc/sys/vm/overcommit_ratio

默认值为:50。这个参数值只有在 vm.overcommit_memory=2 的情况下,这个参数才会生效。该值为物理内存比率,当 overcommit_memory=2 时,进程可使用的 swap 空间不可超过 PM * overcommit_ratio / 100

2.2.6 /proc/[pid]/oom_score

每一个进程都会有的一个值,在 OOM-killer 选择终止进程的时候,会首先终止 oom_score 最高的进程。

2.2.7 /proc/[pid]/oom_adj

/proc/[pid]/oom_adj ,该 pid 进程被 OOM-killer 杀掉的权重,介于 [-17,15] 之间,越高的权重,意味着更可能被 OOM-killer 选中,-17 表示禁止被 kill 掉。

oom_score_adj 的作用和 oom_adj 作用类似,如果设置为 -1000 则表示禁止被 kill 掉。

3. 解决方案

在当前场景下,其实可以接受单个作业被内核杀死而保全 worker 进程。因此要做的很简单,把 worker 进程的 oom_adj 调整为 -17 即可。

 sudo su - root
 echo -17 > /proc/[pid]/oom_adj

 

使用 Shadowsocks-libev 构建网络代理

1. 序

作为一个程序员,总有着一些科学上网的需求。面对纷繁的服务提供商,面对时断时续的网络服务,总还是觉得老话说的有道理:自己动手丰衣足食!

2. 搭建过程

搭建代理服务器的常见手段是在一台境外服务器上启动 Shadowsocks 服务,然后在终端使用客户端连接过去。大致过程可以分为几个简单步骤:

  1. 购买境外服务器。

  2. 搭建 Shadowsocks 服务。

  3. 下载客户端连接。

2.1 购买服务器

在同事的推荐下,在 Vultr1 购买相关的服务。配置和价格如下:

项目 规格
CPU 1 vCore
RAM 1024 M
Storage 25 G SSD
Bandwidth 1000 GB
Location Silicon Valley
OS Ubuntu 18.04
Price $ 5 /month

但就价格来说每年对应人民币在 400 元左右价格偏贵,但是如果考虑到同时能搭建 WordPress 和 Shadowsocks 服务的话相对更容易接受一些。

另外一个好处是灵活,如果这个 IP 被封了还可以在其他地区的机房搭建服务。

2.2 搭建服务

搭建服务只需要按步骤执行如下命令(默认 root 用户)2

 apt-get update
 apt-get install -y shadowsocks-libev

然后编辑配置文件 /etc/shadowsocks-libev/config.json

 {
     "server":"0.0.0.0",
     "server_port":<port>,
     "local_port":1080,
     "password":"<password>",
     "timeout":60,
     "method":"aes-256-cfb"
 }

这里 <server port><password> 根据喜好设置即可。

然后我们启动服务:

 # 重新启动 shadowsocks-libev (以防按照旧的配置启动了服务)
 systemctl restart shadowsocks-libev
 
 # 设置随着操作系统自启动
 systemctl enable shadowsocks-libev.service
 
 # 查看服务状态
 systemctl status shadowsocks-libev.service

另外针对网络拥塞的情况我们还可以使用 BBR 来提升网速,具体的方法为3

 echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
 echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
 sysctl -p

关于 BBR 的原理可参见参考资料4

2.3 下载客户端

Shadowsocks 的客户端可以从 Github 进行下载,具体页面为:

3. 参考资料

使用 Docker 构建工作环境

1. 前言

作为一个程序员,在家加班或者处理紧急需求是一种常态。为了应对这种情况(也为了更好地压榨程序员的剩余时间)大部分公司都会给员工配发笔记本电脑。

新员工入职第一件事情就是配置工作环境,这个工作至少耗费一天的时间。有经验的程序员一般会用一个 dotfile 项目来保存常用的配置文件,比如 .zshrc.vimrc.ssh/config 等。

本文更进一步,使用 Docker 来构建工作环境。

原因在于以下几个方面:

  • 任意电脑都可以快速初始化工作环境。本人不喜欢把公司电脑背回家,一来太重太麻烦,而来丢了坏了还要找你赔。

  • 隔离工作环境。把工作环境当做不同的 profile,以插件的形式加载到环境中,灵活而且干净。

2. 实施方案

为了更好地保障安全性,本人把工作环境的 Docker 分为两个部分:

  • Docker 镜像

  • dotfile (Git 仓库)

本质上是代码和配置分离的策略,镜像可以使用阿里云提供的服务来存储,而 Dockerfile 和 dotfile 则利用 Git 的私有库做版本控制和多机器同步。

2.1 Docker 镜像

以下是本人人生中第一个 Dockerfile:

 ARG UBUNTU_VERSION=18.04
 FROM ubuntu:${UBUNTU_VERSION}
 MAINTAINER wuzhiyu "wuzhiyu@avalon-inc.com"
 
 ###################### SYSTEM SETTINGS ######################
 
 ARG USER_NAME=<account name>
 ARG GROUP_NAME=sudo
 ARG PASSWORD=<password>
 ARG HOME=/home/${USER_NAME}
 ARG HOST_NAME=ubuntu-docker
 
 # 1. update chinese software repository
 RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak
 COPY 18.04.aliyun.sources.list /etc/apt/sources.list
 
 # 2. install necessary software
 ENV DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get install -y sudo man curl inetutils-ping git tmux vim autojump python-pip zsh oathtool expect && apt-get clean && pip install mycli virtualenv virtualenvwrapper
 RUN apt-get update && apt-get install -y locales && apt-get clean && locale-gen en_US.UTF-8
 
 # 3. (1)create a user account without password (2) grant sudo permission by adding account to sudo group (3) set password for the account
 
 RUN adduser --home ${HOME} --ingroup ${GROUP_NAME} --shell /bin/bash --disabled-password --gecos "" ${USER_NAME} && echo "${USER_NAME}:${PASSWORD}" | chpasswd
 RUN chown -R ${USER_NAME}:${GROUP_NAME} ${HOME}
 
 ###################### USER SETTINGS ######################
 
 # set default user and work dir
 USER ${USER_NAME}
 WORKDIR ${HOME}
 ENV USER ${USER_NAME}
 
 # install oh-my-zsh
 RUN export RUNZSH=yes && sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
 
 # config-files -v when docker run
 
 # CMD
 CMD /usr/bin/zsh

这个 Dockerfile 干的事情非常简单,简单来说就是:

  1. 指定基础镜像。

  2. 更新 Ubuntu 的软件源。国情在此,用阿里云的替换。

  3. 使用非交互模式安装必要的软件。

  4. 创建用户账户并授予 sudo 的权限。

  5. 切换到用户账户和家目录。

  6. 安装 oh-my-zsh

  7. 指定默认进入 zsh

构建命令为:

 docker build -t workspace:latest ubuntu

其中 ubuntu 是保存 Dockerfile 和其他文件的上下文目录。

看的出来镜像本身是比较简单的,主要是准备必要的软件和运行账户,下面来介绍一下 dotfile。

2.2 dotfile

dotfile 项目包括不限于以下内容:

  • .zshrc

  • .vimrc

  • .ssh/config

  • 公钥私钥

  • 业务脚本

2.2.1 .zshrc

这个文件是 zshell 启动时默认加载的文件,一般也是我们所有环境配置的公共部分。我们可以通过一些命令来判断加载特定的 profile。

比如:

 # 为 true 表示为 Mac 环境
 [[ $(uname) = "Darwin" ]]
 
 # 为 true 表示为 Docker 环境
 [[ -f /.dockerenv ]]

如果为 docker 环境,我们可以 source 包含业务信息的脚本(比如自动进行两步校验的登录脚本、包含数据库连接串信息的脚本等等),也可以进行环境配置文件的自动更新。

2.2.2 .vimrc

Vim 的配置文件,此处不表。

2.2.3 .ssh/config

常见来说两种使用功能:

  • 自动指定跳板机或端口信息

  • 对应特定应用使用特定私钥

首先一般公司内部连接线上服务器都要求通过指定的跳板机和特定端口(非默认 22)来连接,每次指定自然非常繁琐,通过 .ssh/config 文件则可以大大减轻负担。

 Host *.dev
     HostName %h.xxx.com
     User wuzhiyu
     Port 10086
     ProxyJump <gateway>
     IdentityFile <path_to_private_key>

一般来说,如果我们要连接 master01.dev.xxx.com 这台机器一般要先跳到跳板机,然后使用如下命令:

 ssh -i <path_to_private_key> wuzhiyu@master01.dev.xxx.com -p 10086

但是有了上述配置,我们可以写作:

 ssh master01.dev

跳板机、端口、机器后缀、私钥位置自动带上了。

2.2.4 公钥私钥

接上文,对于不同的版本控制系统使用不同的密钥是很常见的一种场景。比如公司内部用一套密钥,个人 GitHub 账户使用另外一套,把公司的密钥放在 dotfile 私有库进行同步便于配置的保存与迁移。

2.2.5 业务脚本

比如数据库连接串的别称、自动登录脚本等,不再赘述。

3. 实践使用

Docker 运行的时候可以使用如下命令进行启动:

 docker run -it --rm -h docker --network host -v <path_to_dotfile_dir>:/home/wuzhiyu/.config-files:ro -v <path_to_zshrc_file>:/home/wuzhiyu/.zshrc:ro --name workspace workspace:latest /usr/bin/zsh

这里有几点要注意:

  1. 网络模式使用了 -—network host 模式是为了使用宿主机的 VPN 网络。

  2. 加载的时候直接挂在了 .zshrc 文件,这样容器运行 zshell 的时候就可以直接加载初始化的脚本。

  3. 默认指定一下 HOST 名称否则部分软件运行会报错。

记录使用expect遇到的一个坑

记录使用 expect 遇到的一个坑


1. 问题描述

最近写了一个自动化的安装程序,由于要远程操作多台服务器进行安装,所以必然想到了使用 Shell 脚本来自动化。而设计到密码输入的(比如 sudo )则使用 expect 来进行交互,然后被一个小坑浪费了一个下午的时间。

先上代码:

#!/bin/bash
user=${1}
ip=${2}
passwd=${3}


expect -c "
spawn ssh ${user}@${ip} -t 'uname -a'
expect {
    assword: {
        send \"${passwd}\r\"
    }
}
expect eof
"

本质上就是连接到服务器执行命令而已,uname -a 但是脚本竟然报错了:zsh:1: command not found: uname -a(注:真实执行的命令可不是 uname -a 这么简单,这里做示例足够了)。

WTF?

2. 解决过程

想了半天,我一直以为是使用 ssh 登录过去环境变量不对导致的。花了半天时间研究了ssh连接远程主机执行脚本的环境变量问题 这篇文章。虽然没有解决问题,但是文章不错!

苦恼良久,我看到了 send \"${passwd}\r\" 这段代码感到很奇怪。这段 expect 代码是从一个前同事的脚本中扒下来的,整条 expect 命令是由双引号括起来的,字符串参数值替换应该没问题,为什么一定要用转义的双引号呢?

我猜这里可能有什么关键,所以果断把 'uname -a' 改成 \"uname -a\"

结果跑通了!

接下来我又发现 'uname -a' 改成 'uname' 后原来的代码也能跑。

测试了一下午加一个晚上总结出,总结出三种可以跑通的情况:

  1. 不在 expect 命令中而直接以 ssh <user>@<ip> <cmd> 的形式调用。
  2. 'uname -a' 改成 \"uname -a\"
  3. 'uname -a' 改成 'uname'

所以我猜测应该是在使用 expect 时命令体内部在表示 <cmd> 部分出了问题。当命令带 flag 时,单引号没有起到作用,需要使用转义的双引号。

其底层原因我也没时间去探索了,在此 mark 一下吧。

3. 结论

总结来说,expect 命令内部需要用双引号和单引号的还是尽量用转义的双引号吧!

TMUX 自定义配置

QQ20160703-0
TMUX 号称是文本处理的“三大神器”之一,不是没有理由的。最近工作上好多事情都要在服务器上处理,所以用 TMUX 特别频繁。

最近花了点时间自己配置了一下 TMUX,这里权当记录一下吧。

1. 自动加载配置文件

在使用 TMUX 的时候,如果要重新加载配置文件,需要 Ctrl + b 触发,然后输入 : + source-file + ~/.tmux.conf

因为在调试配置的时候需要反复尝试,所以把这个配置做成了快捷键 Ctrl + b + R

bind R source-file ~/.tmux.conf

2. 采用 Vi 模式的快捷键

开启之后在复制模式(copy-mode,Ctrl + b + ?)中可以使用 Vi 模式下的快捷键。

setw -g mode-keys vi

3. 鼠标支持

对于鼠标的支持表现在通过鼠标的点击切换 window、pane 以及通过拖曳更改 pane 的大小。

注意的是 TMUX 版本的不同设置方式也不同。我的 MacBook Pro 使用的是 2.1,而我经常登录的服务器上使用的是 1.6。查看 TMUX 版本的命令是:

tmux -V

如果是 2.1 的话,设置为:

set -g mouse on

如果是 1.6 的话则设置为:

set -g mouse-select-window on
set -g mouse-select-pane on
set -g mouse-resize-pane on

4. 切换 pane 的快捷键

默认的设置中,pane 的切换的快捷键是 Ctrl + b + 方向键,为了更便捷地进行切换,定义了新的快捷键 Alt + 方向键

# 切换为使用 Alt-方向键 切换同一 Window 中的pane
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D

5. 自定义状态栏

TMUX 可以自定义状态栏的颜色和展示信息。首先给出我暂时使用的配置:

# 自动重新编号 window
set -g renumber-windows on

# 设置自动刷新的时间间隔
set -g status-interval 1
# 状态栏左对齐
set -g status-justify left
# 状态栏左侧宽度
set -g status-left-length 20
# 状态栏右侧宽度
set -g status-right-length 50

# 状态栏背景颜色
set -g status-bg '#333333'
# 状态栏前景颜色
set -g status-fg '#ffffff'
# 状态栏左侧显示 session 的名字
set -g status-left '#[bg=#00bb00] [#S] #[default] '
# 状态栏右侧显示时间
#set -g status-right '#[fg=white,bg=#55bb00] [#h] #[fg=white,bg=#009c00] %Y-%m-%d #[fg=white,bg=#007700] %H:%M:%S '
set -g status-right '#[fg=white,bg=#444444] [#h] #[fg=white,bg=#666666] %Y-%m-%d #[fg=white,bg=#888888] %H:%M:%S '

# 当前激活窗口在状态栏的展位格式
setw -g window-status-current-format '#[bg=#ff0000, fg=#ffffff, bold]*[#I] #W*'
# 未激活每个窗口占位的格式
setw -g window-status-format '#[bg=#0000ff, fg=#ffffff] [#I] #W '

有几个配置需要进行说明一下:

5.1 关于时间的配置

上文配置中,我采用了几个时间参数显示在了状态栏的右侧:

参数形式 参数含义
%Y 年:2016
%m 月:07
%d 日:03
%H 小时:02
%M 分钟:19
%S 秒:04

需要注意的是其实这个时间的刷新是有间隔的,我们可以通过设置参数:

set -g status-interval 1

使得状态栏的时间好像每一秒都在变化。

5.2 颜色配置

TMUX 的状态栏中可以按照 #[bg=colourxx,fg=#ffffff] 的形式定义颜色的前景和背景。颜色的类型指定如:blackredgreenyellowbluemagentacyanwhite 。也可以是 colour0 到 colour255。或者形如 #ffffff 的 RGB 格式。同时还可以针对字体进行设置:bright (or bold), dimunderscoreblinkreverse,hidden, or italics

#[default] 可以重置修改后的样式。

5.3 其他配置参数

参数名称 参数含义
#H 完整主机名
#h 不含域名的主机名
#F 窗口的标记
#S session 的名字
#I window 的序号
#W 窗口的名字

此外,我们还可以通过 #(shell_cmd) 的形式运行 shell 命令来增强显示。我们还可以通过 #{VARIABLE} 的形式使用 TMUX 中的各种变量。

  1. 条件变量,例如:#{?session_attached,attached,not attached}。如果 ? 后面的变量存在并且非 0 则返回第一个变量,否则返回第二个。
  2. 变量字符串截取 #{=N:VARIABLE} 的形式获取变量的前 N 个字符, #{=-N:VARIABLE} 获取变量的后 N 个字符。
  3. 如果变量是时间戳类型的,#{t:VARIABLE} 会把时间戳转换为时间字符串。
  4. #{d:VARIABLE} 返回变量的 dirname#{d:VARIABLE} 返回 basename

更多相关配置可见于:FORMATS

6. 我的配置

以下是我的配置,部分配置还是以 2.1 为准。

# 绑定 R 重载配置文件
bind R source-file ~/.tmux.conf

# 采用 vi 模式
setw -g mode-keys vi

# 基本设置
set -g default-terminal "screen-256color"

# 设置序号从1开始
set -g base-index 1
set -g pane-base-index 1

# 关闭状态栏窗口占位的自动命名
setw -g automatic-rename off
set-option -g allow-rename off
setw -g utf8 on
set -g status-utf8 on

# 设定状态栏的位置
set -g status-position bottom

# 状态栏配置 {
    # 自动重新编号 window
    set -g renumber-windows on

    # 设置自动刷新的时间间隔
    set -g status-interval 1
    # 状态栏左对齐
    set -g status-justify left
    # 状态栏左侧宽度
    set -g status-left-length 20
    # 状态栏右侧宽度
    set -g status-right-length 50

    # 状态栏背景颜色
    set -g status-bg '#333333'
    # 状态栏前景颜色
    set -g status-fg '#ffffff'
    # 状态栏左侧显示 session 的名字
    set -g status-left '#[bg=#00bb00] [#S] #[default] '
    # 状态栏右侧显示时间
    #set -g status-right '#[fg=white,bg=#55bb00] [#h] #[fg=white,bg=#009c00] %Y-%m-%d #[fg=white,bg=#007700] %H:%M:%S '
    set -g status-right '#[fg=white,bg=#444444] [#h] #[fg=white,bg=#666666] %Y-%m-%d #[fg=white,bg=#888888] %H:%M:%S '

    # 当前激活窗口在状态栏的展位格式
    setw -g window-status-current-format '#[bg=#ff0000, fg=#ffffff, bold]*[#I] #W*'
    # 未激活每个窗口占位的格式
    setw -g window-status-format '#[bg=#0000ff, fg=#ffffff] [#I] #W '
# }

# tmux 控制相关的配置 {
    # 允许鼠标选取 Window 、Pane 以及 Pane 的大小改变,2.1 时使用如下设置
    set -g mouse on
    # 同上,1.6 时如下设置
    #set -g mouse-select-window on
    #set -g mouse-select-pane on
    #set -g mouse-resize-pane on

    # 切换为使用 Alt-方向键 切换同一 Window 中的pane
    bind -n M-Left select-pane -L
    bind -n M-Right select-pane -R
    bind -n M-Up select-pane -U
    bind -n M-Down select-pane -D
# }

每次学一个这样的高频度软件,都觉得开源世界真尼玛高深莫测,每个软件都有如此复杂的可定制性!还都是免费的!

7. 参考资料

使用 Shell 变量的正确正确姿势

今天发生了在写 Shell脚本的时候发现了一个问题:变量赋值了却取不出来!

当时的代码是这么写的:

V_TS=`date +%Y%m%d`
TABLE_NAME=dwm_xxx_xxx_xxx_metric_day
TEMP_TABLE="es_$TABLE_NAME_$V_TS"

本意是原来的表名加一个前缀 es,加一个后缀日期。结果 TEMP_TABLE 赋值得到的结果是 es_20160303 !!!

仔细一分析,原来在这里把变量名 TABLE_NAME 识别成了 TABLE_NAME_

解决方案也很简单,把

TEMP_TABLE="es_$TABLE_NAME_$V_TS"

改成

TEMP_TABLE="es_${TABLE_NAME}_${V_TS}"

即可,不仅醒目而且消除了歧义!

延伸

扩展一下,上面

V_TS=`date +%Y%m%d`

的写法其实也不太好,用

V_TS=$(date +%Y%m%d)

其实更好。

无独有偶,在计算算数表达式的时候,VAR=$(( 1 + 3 )) 或者 VAR=$[ 1 + 3 ] 就比

VAR=`expr 3 + 1`

来的好。

date 命令小结

最近做的最多的就是把 Hive 查询写到 Shell 脚本中定时调度,常见的有按小时、按天、按周、按月来调度。

所以日期参数的传递就比较重要,这里做一下 date 命令的简单总结。

1. 格式

date [OPTION] [FORMAT]

2. 选项

选项一般是 --date(简写 -d)。

3. 格式

%Y   年,如:2016
%m   月, (01..12)
%d   日,两位
%u   day of week (1..7); 1 is Monday
%V   ISO week number, with Monday as first day of week (01..53)

4. 例子

# 格式化日期
date -d 20160101 +%Y-%m-%d
# 依据某一天进行加减
date -d "-1 days 20160101" +%u
# 文本化的输入
date -d "last sunday" +%Y-%m-%d

Vagrant

1. 简介

Vagrant是一个基于Ruby的工具,用于创建和部署虚拟化开发环境。它 使用Oracle的开源VirtualBox虚拟化系统,使用 Chef创建自动化虚拟环境。

我们可以通过 Vagrant 封装一个 Linux 的开发环境,分发给团队成员。成员可以在自己喜欢的桌面系统(Mac/Windows/Linux)上开发程序,代码却能统一在封装好的环境里运行,非常霸气。

2. 安装

2.1 安装 VirtualBox

sudo apt-get install virtualbox-5.0

2.2 安装 Vagrant

下载地址:http://downloads.vagrantup.com/ 根据提示一步步安装。

sudo apt-get install vagrant

2.3 下载官方封装好的基础镜像:

如果你要其他系统的镜像,可以来这里下载

3. 相关操作

3.1 添加镜像

如果下载镜像的存放路径为 ~/path/to/image.box,那么添加 box 的方法为:

vagrant box add <BOX_NAME> ~/path/to/image.box

3.2 初始化开发环境

cd ~/path/to/dev_dir
vagrant init <BOX_NAME> #切换目录
vagrant up #初始化

接着配置环境参数。

vagrant ssh
cd /vagrant

~/path/to/dev_dir 目录对应虚拟机中的 /vagrant

3.3 其他设置

Vagrant 初始化成功后,会在初始化的目录里生成一个 Vagrantfile 的配置文件,可以修改配置文件进行个性化的定制。

Vagrant 默认是使用端口映射方式将虚拟机的端口映射本地从而实现类似 http://localhost:80 这种访问方式。这种方式比较麻烦,新开和修改端口的时候都得编辑。相比较而言,host-only 模式显得方便多了。打开 Vagrantfile,将下面这行的注释去掉(移除#)并保存:

config.vm.network :private_network, ip: "192.168.33.10"

重启虚拟机,这样我们就能用 192.168.33.10 访问这台机器了,你可以把 IP 改成其他地址,只要不产生冲突就行。

3.4 打包

vagrant package

4. 常用命令

vagrant init  # 初始化 
vagrant up  # 启动虚拟机 
vagrant halt  # 关闭虚拟机 
vagrant reload  # 重启虚拟机 
vagrant ssh  # SSH 至虚拟机 
vagrant status  # 查看虚拟机运行状态 
vagrant destroy  # 销毁当前虚拟机

5. 注意事项

使用 Apache/Nginx 时会出现诸如图片修改后但页面刷新仍然是旧文件的情况,是由于静态文件缓存造成的。需要对虚拟机里的 Apache/Nginx 配置文件进行修改:

# Apache 配置添加: 
EnableSendfile off 
# Nginx 配置添加: 
sendfile off;

参考文章

使用 Vagrant 打造跨平台开发环境

SSH 公钥登录与超时断开

最近趁着阿里云针对学生半价买了一个半年的云主机,用下来有两个问题感觉比较蛋疼,一个是每次登录都要输入用户名密码,另一个就是超时自动断开。

公钥登录

ssh 远程连接服务器的验证方法有两种:密码或者公钥。免输密码的解决方法其实很简单,用 expect 命令就可以了。这里介绍一下如何将公钥导入服务器,免除登录时的密码验证。

  1. 前提条件当然是客户端创建了公钥,如果没有请使用 ssh-keygen
  2. 将客户端机器的公钥拷贝至服务器对应用户的家目录下:scp id_rsa.pub user@target_ip:~/
  3. 在服务器上创建 .ssh 目录,并修改权限:mkdir ~/.ssh && chmod 700 ~/.ssh
  4. 创建文件 authorized_keys 并将客户端的公钥写入:cat id_rsa.pub >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

超时自动断开

阿里云主机大概一分钟左右没有输入客户端就会死在那里。所以需要设置一下 /etc/ssh/sshd_config 来延长超时时间,配置的内容就两条:

ClientAliveInterval 30
ClientAliveCountMax 200

然后重启一下 sshd 服务,反正我是重启了云主机,然后就行了。

以上。

一个跨平台的离线文档浏览器——Zeal

引言

对于一个程序员来说,查阅编程语言或者框架的手册是家常便饭,但是频繁打开浏览器总是一件很麻烦的事情。

之前在学 Ruby on Rails 时看到了 MacBook 上的 Dash,用起来真方便!可惜也只是 MacBook 上的,我等穷鬼只能羡慕。

小小搜索了一下,在 Github 上发现了一个名为 Zeal 的项目。

Zeal

这个软件的有以下几个特点:

  1. 跨平台。Windows 和 Linux 下都有对应版本。
  2. 支持众多的框架和语言。听过的没听过的都能在这里找到,还可以定制自己的语言手册。
  3. 与集成开发环境和文本编辑器集成。
  4. 支持搜索。

放一张图片:
示例图

更多资料