flask cookie and session

因为HTTP协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。

session

from flask import Flask, render_template_string, \
	session, request, redirect, url_for
app = Flask(__name__)
app.secret_key = 'F12Zr47j\3yX R~X@H!jLwf/T'

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/login')
def login():
	page = '''
	<form action="" method="post">
		<p>name: <input type="text" name="user_name" /></p>
		<input type="submit" value="Submit" />
	</form>
	'''
	return render_template_string(page)
	
@app.route('/do_login', methods=['POST'])
def do_login():
	name = request.form.get('user_name')
	session['user_name'] = name
	return 'success'

@app.route('/show')
def show():
	return session['user_name']

@app.route('/logout')
def logout():
	session.pop('user_name', None)
	return redirect(url_for('login'))

if __name__ == '__main__':
	app.run(debug=True)

设置 session 过期时间

from datetime import timedelta from flask import session, app session.permanent = True app.permanent_session_lifetime = timedelta(minutes=5)

from flask import Flask, request, Response, make_response
import time
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/add')
def login():
	res = Response('add cookies')
	res.set_cookie(key='name', value='letian', expires=time.time()+6*60)
	return res

@app.route('/show')
def show():
	return request.cookies.__str__()

@app.route('/del')
def del_cookie():
	res = Response('delete cookies')
	res.set_cookie('name', '', expires=0)
	# print res.headers
	# print res.data
	return res
if __name__ == '__main__':
	app.run(host='0.0.0.0', debug=True)

reference


2017-07-27 Flask , Web , Python

MySQL 中 KEY vs PRIMARY KEY vs UNIQUE KEY vs INDEX 的区别

对于题目中提出的问题,可以拆分来一步步解决。在 MySQL 中 KEYINDEX 是同义。那这个问题就可以简化为 PRIMARY KEY,UNIQUE KEY 和 INDEX 的区别。而这三者也正好是索引的划分,主键索引,唯一索引和普通索引(INDEX)。

使用 INDEX 来加速从数据库中读取数据。INDEX 通常加在那些 JOIN, WHERE,和 ORDER BY 子句的列上。

创建索引时,需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。 实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。

索引也有它的缺点:虽然索引提高了查询速度,却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。

MySQL 中 KEY 与 INDEX 区别

KEY 通常是 INDEX 同义词。如果关键字属性 PRIMARY KEY 在列定义中已给定,则 PRIMARY KEY 也可以只指定为KEY。这么做的目的是与其它数据库系统兼容。 PRIMARY KEY 是一个唯一 KEY,此时,所有的关键字列必须定义为NOT NULL。如果这些列没有被明确地定义为NOT NULL,MySQL应隐含地定义这些列。

KEY 即键值,是关系模型理论中的一部份,比如有主键(PRIMARY KEY),外键(Foreign KEY)等,用于数据完整性检否与唯一性约束等。而 INDEX 则处于实现层面,比如可以对表个的任意列建立索引,那么当建立索引的列处于SQL语句中的Where条件中时,就可以得到快速的数据定位,从而快速检索。至于UNIQUE INDEX,则只是属于INDEX中的一种而已,建立了UNIQUE INDEX表示此列数据不可重复,猜想MySQL对UNIQUE INDEX类型的索引可以做进一步特殊优化吧。

于是,在设计表的时候,KEY只是要处于模型层面的,而当需要进行查询优化,则对相关列建立索引即可。

KEY

KEY 是数据库的物理结构,包含两层含义,一是约束,偏重于约束和规范数据库的结构完整性,二是索引,辅助查询。

  • primary key 有两个作用,一是约束作用(constraint),用来规范一个存储主键和唯一性,但同时也在此key上建立了一个index;
  • unique key 也有两个作用,一是约束作用(constraint),规范数据的唯一性,但同时也在这个key上建立了一个index;
  • foreign key也有两个作用,一是约束作用(constraint),规范数据的引用完整性,但同时也在这个key上建立了一个index;

可见,key是同时具有constraint和index的意义.

INDEX

INDEX 也是数据库的物理结构,但他只有辅助查询作用,它会在创建时占用另外的空间。索引分为前缀索引、全文索引等。索引只是索引,不会去约束索引字段的行为。

PRIMARY KEY 和 UNIQUE KEY 的区别

PRIMARY KEYs(主键) 和 UNIQUE KEYs(唯一键约束) 是类似的, PRIMARY KEY通常是一列,也有可能多列,通常由他来决定一行数据(row)。 一张表只能有一个 PRIMARY KEY,但可以有很多 UNIQUE KEY。 当给一列设置为 UNIQUE KEY 之后,不能有两行在该列上有相同的数据。 PRIMARY KEY 不允许有 NULL值,但是 UNIQUE KEY 可以。

修改表 `ALTER TABLE table_name ADD PRIMARY KEY(column_name, …)

总结,相同点:

  • PRIMARY KEY 和 UNIQUE KEY 都是用来保证列上数据的为原型
  • 都可以在一列或者多列上加

差异点:

  • 同一张表 PRIMARY KEY 只能有一个, UNIQUE KEY可以有多个
  • PRIMARY KEY 不能有空值, UNIQUE KEY 可以有。如果 PRIMARY KEY 的1个或多个列为NULL,在增加PRIMARY KEY时,列自动更改为 NOT NULL 。而UNIQUE KEY 对列没有要求是通过参考索引实施的,如果插入的值均为NULL,则根据索引的原理,全NULL值不被记录在索引上,所以插入全NULL值时,可以有重复的,而其他的则不能插入重复值。

    alter table t add constraint uk_t_1 UNIQUE (a,b); insert into t (a ,b ) values (null,1); # 不能重复 insert into t (a ,b ) values (null,null);#可以重复

在MySQL中,对于一个PRIMARY KEY的列,MySQL已经自动对其建立了UNIQUE INDEX,无需重复再在上面建立索引了。

网上关于 PRIMARY KEY 和 UNIQUE INDEX 的一段解释:

Note that “PRIMARY” is called PRIMARY KEY not INDEX. 
KEY is something on the logical level, describes your table and database design (i.e. enforces referential integrity …) 
INDEX is something on the physical level, helps improve access time for table operations. 
Behind every PK there is (usually) UNIQUE INDEX created (automatically). 

操作索引

建立索引会占用磁盘空间的索引文件。

CREATE INDEX IndexName ON mytable(username(length));

如果是 CHAR,VARCHAR 类型,length 可以小于字段实际长度;如果是 BLOB 和 TEXT类型,必须指定 length。

在创建表时创建索引:

CREATE TABLE mytable( 
	ID INT NOT NULL,   
	username VARCHAR(15) NOT NULL,
	INDEX [INDEXName] (username(length)) 
);  

删除索引

DROP INDEX [INDEXName] ON mytable;

2017-07-24 MySQL , Linux , Key , Index

树莓派中安装MySQL 5.7

最近用到 MySQL 5.7 把所有设备上的 MySQL 版本都升级到了最新,在 Ubuntu/Debian 上升级MySQL 5.7 的内容可以在之前的文章看到。现在记录一下树莓派中升级 MySQL 的步骤。使用到 MySQL 5.7 主要也是因为其支持的新数据类型,之前项目用到了,迁移的时候会遇到问题。在网上寻找解决方案的时候遇到了一个和我遭遇差不多的,需要使用到 MySQL 5.7+ 才支持的 JSON data-type。

在 respberry pi 官方的源中,只有稳定版的 5.5 MySQL,如果要用到最新的版本只能够自己手动编译更新安装,幸而 Debian 提供了编译好的安装包 ,根据以下步骤就能够安装上。

下载依赖

apt-get 安装

sudo apt-get install libaio1 libaio-dev libhtml-template-perl libevent-core-2.0-5

需要安装新版本的 gcc,所以要从 stretch 源中拉取

sudo vim /etc/apt/sources.list
# change jessie to stretch

sudo apt-get update
sudo apt-get install gcc-6 g++-6

# change stretch back to jessie 
sudo apt-get update

然后安装两个依赖

wget http://ftp.debian.org/debian/pool/main/m/mecab/libmecab2_0.996-3.1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/l/lz4/liblz4-1_0.0~r131-2+b1_armhf.deb
sudo dpkg -i libmecab2_0.996-3.1_armhf.deb
sudo dpkg -i liblz4-1_0.0~r131-2+b1_armhf.deb

移除原来的版本

sudo apt-get --purge remove mysql-server # and a lot of like client, common etc

安装最新版

下载 deb 并安装

wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqlclient-dev_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqlclient20_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqld-dev_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-client-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-client-core-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-server-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-server-core-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-defaults/mysql-common_5.8+1.0.2_all.deb

sudo dpkg -i mysql-common_5.8+1.0.2_all.deb
sudo dpkg -i mysql-client-core-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-client-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-server-core-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-server-5.7_5.7.18-1_armhf.deb

注意这里的版本号,可能之后会更新,去 ftp 中获取最新的版本在这里替换即可。

安装完成之后 sudo reboot

在启动之后

mysql_upgrade -u root -p --force
# 然后输入密码
sudo service mysql restart

安装完毕。

reference


2017-07-23 MySQL , Linux , RespberryPi

使用 rsync 增量同步备份文件

rsync 全名 Remote Sync,是类unix系统下的数据镜像备份工具。

rsync - a fast, versatile, remote (and local) file-copying tool

rsync是一个功能非常强大的工具,其命令也有很多功能特色选项

它的特性如下:

  • 可以从远程或者本地镜像保存整个目录树和文件系统。
  • 可以保持文件原来的权限、时间、所有者、组信息、软硬链接等等。
  • 无须特殊权限即可安装。
  • 快速:他要比 scp (Secure Copy) 要快;第一次同步时 rsync 会复制全部内容,但在下一次只传输修改过的文件。rsync 在传输数据的过程中可以实行压缩及解压缩操作,可以使用更少的带宽。
  • 安全:可以使用scp、ssh等方式来传输文件,当然也可以通过直接的socket连接。
  • 支持匿名传输,以方便进行网站镜像。

rysnc 的官方网站:http://rsync.samba.org/ ,可以从上面得到最新的版本。

rsync的使用

Rsync的命令格式可以为以下六种:

rsync [OPTION]... SRC DEST
rsync [OPTION]... SRC [USER@]HOST:DEST
rsync [OPTION]... [USER@]HOST:SRC DEST
rsync [OPTION]... [USER@]HOST::SRC DEST
rsync [OPTION]... SRC [USER@]HOST::DEST
rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST] 

rsync 有六种不同的工作模式:

  1. 拷贝本地文件;当SRC和DEST路径信息都不包含有单个冒号”:”分隔符时就启动这种工作模式。

  2. 使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DEST 路径地址包含单个冒号”:”分隔符时启动该模式。

  3. 使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC 地址路径包含单个冒号”:”分隔符时启动该模式。

  4. 从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含”::”分隔符时启动该模式。

  5. 从本地机器拷贝文件到远程rsync服务器中。当DEST路径信息包含”::”分隔符时启动该模式。

  6. 列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。

这6种方式看似复杂,其实只要记住一些常用参数,然后记住一些常用方法就能够将 rsync 利用起来。

可以man rsync 参考 rsync 文档,了解详细的使用方法,下面解析一些参数的使用

常用的几个参数

-v  verbose 详细输出
-a 	归档模式,递归方式传输文件,并保持连接,权限,用户和组,时间信息
-z  压缩文件传输
-h  human-readable, 输出友好

rsync参数的具体解释如下:

-v, --verbose 详细模式输出
-q, --quiet 精简输出模式
-c, --checksum 打开校验开关,强制对文件传输进行校验
-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于-rlptgoD
-r, --recursive 对子目录以递归模式处理
-R, --relative 使用相对路径信息
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用--suffix选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename)存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件)
-l, --links 保留软链结
-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links 仅仅拷贝指向SRC路径目录树以外的链结
--safe-links 忽略指向SRC路径目录树以外的链结
-H, --hard-links 保留硬链结
-p, --perms 保持文件权限
-o, --owner 保持文件属主信息
-g, --group 保持文件属组信息
-D, --devices 保持设备文件信息
-t, --times 保持文件时间信息
-S, --sparse 对稀疏文件进行特殊处理以节省DST的空间
-n, --dry-run现实哪些文件将被传输
-W, --whole-file 拷贝文件,不进行增量检测
-x, --one-file-system 不要跨越文件系统边界
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是700字节
-e, --rsh=COMMAND 指定使用rsh、ssh方式进行数据同步
--rsync-path=PATH 指定远程服务器上的rsync命令所在路径信息
-C, --cvs-exclude 使用和CVS一样的方法自动忽略文件,用来排除那些不希望传输的文件
--existing 仅仅更新那些已经存在于DST的文件,而不备份那些新创建的文件
--delete 删除那些DST中SRC没有的文件
--delete-excluded 同样删除接收端那些被该选项指定排除的文件
--delete-after 传输结束以后再删除
--ignore-errors 及时出现IO错误也进行删除
--max-delete=NUM 最多删除NUM个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输
--force 强制删除目录,即使不为空
--numeric-ids 不将数字的用户和组ID匹配为用户名和组名
--timeout=TIME IP超时时间,单位为秒
-I, --ignore-times 不跳过那些有同样的时间和长度的文件
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为0
-T --temp-dir=DIR 在DIR中创建临时文件
--compare-dest=DIR 同样比较DIR中的文件来决定是否需要备份
-P 等同于 --partial
--progress 显示备份过程
-z, --compress 对备份的文件在传输时进行压缩处理
--exclude=PATTERN 指定排除不需要传输的文件模式
--include=PATTERN 指定不排除而需要传输的文件模式
--exclude-from=FILE 排除FILE中指定模式的文件
--include-from=FILE 不排除FILE指定模式匹配的文件
--version 打印版本信息

Example

下面举例说明rsync的六种不同工作模式:

拷贝本地文件

当SRC和DES路径信息都不包含有单个冒号”:”分隔符时就启动这种工作模式。

同步文件

rsync -ahvz backup.tar.gz  /backups/  # DESC 不存在时自动创建

将备份文件同步到 /backups/ 目录下。

同步目录

rsync -avzh /home/src /backups/files/

/home/src 目录下的文件同步发送到 /backups/files 目录下。记住如果目标地址没有 src 目录,rsync 会自动创建改文件夹。

rsync -avz /home/src/ /backups/files/

SRC 路径末尾的 / 表示不自动创建 DEST 文件夹,在 man rsync 中的解释就是末尾的 / 表示”拷贝当前目录下的文件” ,而不是”拷贝当前的目录“,

远程 shell 拷贝到远程

使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DES路径地址包含单个冒号”:”分隔符时启动该模式。

rsync -avz /local/path/  user@remoteip:/path/to/files/

将本地 /local/path/ 中的文件同步备份到远程 /path/to/files/ 目录。

远程 shell 拷贝到本地

使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC地址路径包含单个冒号”:”分隔符时启动该模式。

rsync -avz user@remoteip:/home/user/src  ./src

远程 rsync 服务器拷贝到本地

从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含”::”分隔符时启动该模式。

rsync -av user@remoteip::www  /databack

拷贝本地文件到远程

从本地机器拷贝文件到远程rsync服务器中。当DES路径信息包含”::”分隔符时启动该模式。

rsync -av /databack user@remoteip::www

文件列表

列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。

rsync -v rsync://remoteip /www 

rsync 使用更改端口

经常遇见的一种情况就是 ssh 更改了默认 22 端口,这个时候使用 -e 参数即可。

rsync有两种常用的认证方式,一种为rsync-daemon方式,另外一种则是ssh。

ssh方式比较缺乏灵活性 一般为首选,但当远端服务器的ssh默认端口被修改后,rsync时找不到一个合适的方法来输入对方ssh服务端口号。

比如现在向机器 remoteip 传送文件,但此时 remoteip 的 ssh 端口已经不是默认的22 端口。

键入命令

rsync /local/path user@remoteip:/path/to/files/ # 出现错误

rsync中的命令 参数 -e, --rsh=COMMAND 指定使用rsh、ssh方式进行数据同步。

参数的作用是可以使用户自由选择欲使用的shell程序来连接远端服务器,当然也可以设置成使用默认的ssh来连接,但是这样我们就可以加入ssh的参数了。

现在命令可以这样写了:

rsync -avz -e "ssh -p $port" /local/path/ user@remoteip:/path/to/files/

显示备份进度

可以使用 --progress 选项来显示进度

rsync -avzhe ssh --progress /home/files/ root@remoteip:/path/to/files/

限制备份文件最大值

设置 Max size 备份文件

rsync -avzhe ssh --max-size='2000k' /var/lib/rpm/ root@remoteip:/root/tmprpm	

备份结束后自动删除本地文件

rsync --remove-source-files -zvh backup.tar /tmp/backups/	

设置备份带宽

rsync --bwlimit=100 -avzhe ssh /var/lib/rpm/ root@remoteip:/root/tmprpm/	

参考


2017-07-22 Linux , rsync , scp

Tmux introduction

Tmux 是一个很棒的终端复用工具,和 screen 命令类似,但是 Tmux 终极的分屏功能要比 screen 强大很多,当然入门也比 screen 要高很多。

Tmux主要包括以下几个模块:

  • session 会话: 一个服务器连接可以包含多个会话
  • window 窗口: 一个会话可以包含多个窗口
  • pane 面板: 一个窗口可以包含多个面板,如果桌面足够大可以充分利用面板达到非常强大的分屏

install

Ubuntu/Debian 系下

sudo apt-get install tmux

基础概念

Tmux 的前缀快捷键

Tmux 的快捷键前缀(Prefix), 为了使自身的快捷键和其他软件的快捷键互不干扰,Tmux 提供了一个快捷键前缀,和screen默认激活控制台的Ctrl+a不同,Tmux默认的是Ctrl+b。当想要使用快捷键时,需要先按下快捷键前缀,然后再按下快捷键。Tmux 所使用的快捷键前缀默认是组合键 Ctrl-b(同时按下 Ctrl 键和 b 键)。 例如,假如你想通过快捷键列出当前 Tmux 中的会话(对应的快捷键是 s),那么你只需要做以下几步:

  1. 按下组合键 Ctrl-b (Tmux 快捷键前缀)
  2. 放开组合键 Ctrl-b
  3. 按下 s 键

使用快捷键之后就可以执行一些相应的指令了。当然如果你不习惯使用Ctrl+b,也可以在~/.Tmux文件中加入以下内容把快捷键变为Ctrl+a,或者其他快捷键:

# Set prefix key to Ctrl-a
unbind-key C-b
set-option -g prefix C-a

Tmux 的配置文件

每当开启一个新的会话时,Tmux 都会先读取 ~/.tmux.conf 这个文件。该文件中存放的就是对 Tmux 的配置。

如果你希望新的配置项能够立即生效,那么你可以将下面这一行配置加入到文件 ~/.tmux.conf 中。

bind r source-file ~/.tmux.conf \; display-message "tmux config reloaded" # create new short cut to reload tmux.conf

这样配置了之后,每当向 ~/.tmux.conf 文件中添加了新的配置,只需要按下 <prefix> r 就可以重新加载配置并使新的配置生效,从而免去了开启一个新的会话。

以下所有的操作都是激活控制台之后,即键入 <prefix> 前提下才可以使用的命令

基本操作

<prefix> ?    列出所有快捷键;按q返回    
<prefix> d    脱离当前会话,可暂时返回Shell界面,输入Tmux attach能够重新进入之前会话    
<prefix> s    选择并切换会话;在同时开启了多个会话时使用    
<prefix> D    选择要脱离的会话;在同时开启了多个会话时使用    
<prefix> :    进入命令行模式;此时可输入支持的命令,例如kill-server所有Tmux会话    
<prefix> [    复制模式,光标移动到复制内容位置,空格键开始,方向键选择复制,回车确认,q/Esc退出    
<prefix> ]    进入粘贴模式,粘贴之前复制的内容,按q/Esc退出    
<prefix> = 	选择性粘贴缓冲区中内容
<prefix> ~    列出提示信息缓存;其中包含了之前Tmux返回的各种提示信息    
<prefix> t    显示当前的时间    
<prefix> Ctrl+z    挂起当前会话    

session 相关

tmux 		  						创建session
tmux new -s $session_name  			创建并指定 session 名字
tmux ls  							列出存在的 session,包括 session 中存在的 windows
tmux attach -t session_name 		进入指定会话session_name
<prefix> :kill-session  			删除 session
<prefix> d 							临时退出session,回话在后台运行,可以通过 attach 进入指定的会话
tmux a -t $session_name  			进入已存在的session
<prefix> :kill-server  				删除所有session
tmux kill-session -t $session_name 	删除指定session
<prefix> s  	从列表中选择session
<prefix> $  	重命名session

窗口相关

window(窗口)在 session 里,一个 session 可以有N个window,并且window可以在不同的session里移动。 window 可以看成是一个 session 的不同 tab。

<prefix> c 		创建window
<prefix> & 		删除window
<prefix> n 		下一个window
<prefix> p 		上一个window
<prefix> w 		列出现在开启的 window
<prefix> , 		重命名window
<prefix> f 		在多个window里搜索关键字
<prefix> l 	    last window 在相邻的两个window里切换
<prefix> 0,1,2  在window之间切换

pane 相关

pane在window里,可以有N个pane,并且pane可以在不同的window里移动、合并、拆分

创建pane

<prefix> " 		横切split pane horizontal,后面会 remap 这个键
<prefix> %      竖切split pane vertical,后面 remap 这个键
<prefix> o 		按顺序在pane之间移动
<prefix> x      删除pane
<prefix> z      最大化pane和恢复原状
<prefix> !      移动pane至window
<prefix> "空格" 更换pane排版
<prefix> { 		移动pane往左边,往上面
<prefix> } 		移动pane往右边,往下面
<prefix> q 		显示pane编号,在显示编号的时候按对应数字可以切换到该pane,这个操作太复杂,后面remap
<prefix> 方向键上下左右   	上下左右选择pane

调整pane的大小

<prefix> :resize-pane -U #向上
<prefix> :resize-pane -D #向下
<prefix> :resize-pane -L #向左
<prefix> :resize-pane -R #向右
<prefix> :resize-pane -D 20 (Resizes the current pane down by 20 cells)
<prefix> :resize-pane -U 20 (Resizes the current pane upward by 20 cells)
<prefix> :resize-pane -L 20 (Resizes the current pane left by 20 cells)
<prefix> :resize-pane -R 20 (Resizes the current pane right by 20 cells)
<prefix> :resize-pane -t 2 20 (Resizes the pane with the id of 2 down by 20 cells)
<prefix> :resize-pane -t -L 20 (Resizes the pane with the id of 2 left by 20 cells)

其他低频率操作

在上下左右的调整里,最后的参数可以加数字 用以控制移动的大小,例如:

<prefix> :resize-pane -D 50

移动pane合并至某个window

<prefix> :join-pane -t $window_name

列出缓冲区目标

<prefix> :list-buffer

查看缓冲区内容

<prefix> :show-buffer

vi模式

<prefix> :set mode-keys vi

快捷键帮助

<prefix> ? (<prefix> :list-keys)

Tmux内置命令帮助

<prefix> :list-commands

TIPS

让你的 Tmux 更加高效,一下内容都可以编辑进 ~/.tmux.conf 用来进一步自定义 Tmux 的行为。 默认的 Tmux 有很多操作方式可能比较 awkward, 只有自己配置让自己熟悉他的行为之后才能让 Tmux 展现出最高效的部分。

自定义 Prefix

在前面也说过, Tmux 默认的 Prefix 是 Ctrl + b,可以按照自己的习惯设定 Prefix 快捷键,很多人将 Caps 和 Ctrl 互换,并且将 Prefix 定义为 Ctrl + a, 我自己使用了以下还是不怎么习惯,所以我将 Prefix 定义成了 Ctrl + \

定义分割pane的快捷键

默认情况下 Tmux 使用 " 来垂直分割成上下两部分,使用 % 来水平分割成左右两部分,但是这两个键需要 Shift+',以及很难按到的%,不如直观上使用 |- 来分割pane。

# split panes using | and -
bind | split-window -h
bind - split-window -v
unbind '"'
unbind %

在pane中移动

在 pane 中移动是最高频的操作了,默认 Tmux 的行为需要每一次都按 <prefix>,这样导致每一次都非常麻烦,所以在配置中定义 M-<direction> (M 代表 Meta,也就是键盘上的 Alt 键), 这样每一次只需要按 Alt+h,就能够移动到左边的 pane。

# switch panes using Alt-arrow without prefix
bind -n M-h select-pane -L
bind -n M-l select-pane -R
bind -n M-k select-pane -U
bind -n M-j select-pane -D

这里另外推荐一个 Plugin,如果不想自己配置,可以使用这个插件vim-tmux-navigator ,这个插件做到了在 pane 中移动就像在 vim 中一样,并且可以和 vim 无缝衔接。

reference


2017-07-21 Tmux , Linux

将 MySQL 升级到 5.7

这些天折腾 Django 的时候用到了 MySQL,然而本地和VPS 上使用的版本不一致,本地使用了 5.7 版本,而 VPS 上使用了 5.5 的老版本,在数据迁移的时候遇到了 5.5 版本下不支持 DATETIME(6) 这样的数据类型。 DATETIME(6) 用来保存精确到微秒的时间。

环境:

系统:Debian 7, 按道理 Ubuntu/Debian 系应该都可以

无奈只能升级 MySQL 到 5.7 , 结果也比较顺利,官方有很详细 的升级说明:

wget http://dev.mysql.com/get/mysql-apt-config_x.y.z-1_all.deb  # 从官网找到最新的版本,上面的链接中有
sudo dpkg -i mysql-apt-config_x.y.z-1_all.deb
sudo apt-get update
sudo apt-get install mysql-server

在中间弹出配置框时选择 5.7 ,应用,即可

mysql --version

最后记得运行

sudo mysql_upgrade -u root -p

reference


2017-07-20 MySQL , Linux

Docker 入门

Docker 是一个能够把开发环境的应用程序自动部署到容器的开源引擎。该引擎的目标是提供一个轻量、快速的环境,能够运行开发者的程序,并方便高效地将程序从开发者的笔记本部署到测试环境,然后再部署到生产环境。

Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。

Docker 使用客户端-服务端 C/S 架构,使用远程 API 来管理和创建 Docker 容器。

Docker 容器通过 Docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类。

Docker 官网:http://www.docker.com

Github Docker 源码:https://github.com/docker/docker

名词 解释
Docker 镜像(Images) Docker 镜像是用于创建 Docker 容器的模板。可以使用 docker images 来查看镜像
Docker 容器(Container) 容器是独立运行的一个或一组应用。可以使用 docker ps -a 来查看 container
Docker 客户端(Client) Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。
Docker 主机(Host) 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker 仓库(Registry) Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。 Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用
Docker Machine Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。

安装

Ubuntu

Docker 要求 Ubuntu 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的 Ubuntu 版本是否支持 Docker。 通过 uname -r 命令查看你当前的内核版本。通过如下命令安装

wget -qO- https://get.docker.com/ | sh

启动

sudo service docker start

启动测试运行 hello-world

docker run hello-world

Raspberry Pi 树莓派

Hypriot team 提供了一个可安装的 Package,可以不必自己编译安装:

$ curl -ks https://packagecloud.io/install/repositories/hypriot/schatzkiste/script.deb.sh | sudo bash
$ sudo apt-get install docker-hypriot=1.10.3-1
$ sudo sh -c 'usermod -ag docker $sudo_user'
$ sudo systemctl enable docker.service

安装完后

pi@raspberrypi ~ $ sudo docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 1.10.3
Storage Driver: overlay
 Backing Filesystem: extfs
Execution Driver: native-0.2
Logging Driver: json-file
Plugins: 
 Volume: local
 Network: bridge null host
Kernel Version: 4.1.19-v7+
Operating System: Raspbian GNU/Linux 8 (jessie)
OSType: linux
Architecture: armv7l
CPUs: 4
Total Memory: 925.8 MiB
Name: raspberrypi
ID: UMZZ:ZYZY:TLB2:DKK7:GY6V:SYW3:JZZD:7L4X:JNXY:HNRQ:PFFO:K4X5
Debug mode (server): true
 File Descriptors: 11
 Goroutines: 20
 System Time: 2017-07-16T14:07:39.946162928Z
 EventsListeners: 0
 Init SHA1: 0db326fc09273474242804e87e11e1d9930fb95b
 Init Path: /usr/lib/docker/dockerinit
 Docker Root Dir: /var/lib/docker
WARNING: No memory limit support
WARNING: No swap limit support
WARNING: No oom kill disable support
WARNING: No cpu cfs quota support
WARNING: No cpu cfs period support

查看版本

pi@raspberrypi ~ $ sudo docker version 
Client:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.4.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 22:23:48 2016
 OS/Arch:      linux/arm

Server:
 Version:      1.10.3
 API version:  1.22
 Go version:   go1.4.3
 Git commit:   20f81dd
 Built:        Thu Mar 10 22:23:48 2016
 OS/Arch:      linux/arm

参考文档, 有关树莓派从 wheezy 升级到 Debian 8 Jessie 的内容也可以参考该链接

$ sudo sed -i 's/wheezy/jessie/' /etc/apt/sources.list
$ sudo sed -i 's/wheezy/jessie/' /etc/apt/sources.list.d/raspi.list 
$ sudo apt-get update && sudo apt-get -y upgrade # answer 'y' to upcoming questions 
$ sudo apt-get -y dist-upgrade # answer 'y' to upcoming questions
$ sudo init 6
$ sudo apt-get -y autoremove
$ sudo apt-get -y purge $(dpkg -l | awk '/^rc/ { print $2 }')
$ sudo init 6

Linux Mint

Linux Mint 下安装的时候使用 Ubuntu 下那种方式的时候没有安装成功,网上查说源中的内容有些问题,使用自己的添加的 Repository 才可以:

# First import the GPG key
 
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 \
      --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
 
# Next, point the package manager to the official Docker repository
 
sudo apt-add-repository 'deb https://apt.dockerproject.org/repo ubuntu-xenial main'
 
# Update the package database
 
sudo apt update

# 安装必要的包
sudo apt install linux-image-generic linux-image-extra-virtual

# 安装docker
sudo apt install docker-engine

最后安装成功

 einverne@ev  ~  sudo docker version
[sudo] password for einverne: 
Client:
 Version:      17.05.0-ce
 API version:  1.29
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:10:54 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.05.0-ce
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:10:54 2017
 OS/Arch:      linux/amd64
 Experimental: false

体验

镜像是 Docker 的三大组件之一,Docker 官方托管了一个镜像的 Hub,可以从上面获取镜像。

可以使用 sudo docker search [] 来搜索镜像

获取镜像

可以使用 docker pull 从仓库中获取所需要的镜像,比如从官方仓库下载 ubuntu 12.04 的镜像:

sudo docker pull ubuntu:12.04

实际该命令相当与 sudo docker pull registry.hub.docker.com/ubuntu:12.04,即从注册的服务器的 Ubuntu Repository 中拉取 12.04 的镜像。

在国内拉去镜像的速度一般不是很快,可以使用官方的镜像加速,其他网易,阿里都有提供,但是建议还是使用官方镜像

docker pull registry.docker-cn.com/myname/myrepo:mytag

比如说:

docker pull registry.docker-cn.com/library/ubuntu:16.04

Docker Store 是发现 Docker 镜像的新地方。

查看镜像

当拉取完成之后可以使用 sudo docker images 来查看本地的镜像列表

$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              1e5ab59102ce        6 days ago          108MB
redis               latest              4e482b286430        3 months ago        99MB

列出的信息中,可以看到几个字段

  • REPOSITORY 镜像名字,比如 nginx
  • TAG 镜像的标记, latest 或者特定版本号
  • IMAGE ID 镜像的唯一标示
  • CREATED
  • SIZE

默认 docker images 只会显示顶层镜像,如果希望显示包含中间层镜像在内的所有镜像,需要添加 -a 参数

docker images -a

docker images 命令有 -f 参数用来过滤,比如列出 虚悬镜像 dangling image 可以使用

$ sudo docker images -f dangling=true
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              8e7492c7cb6a        28 minutes ago      122MB

虚悬镜像是指因为官方镜像维护,发布新版本之后有些本地镜像名被取消,虚悬镜像已经没有价值,可以随意删除

sudo docker rmi $(sudo docker images -q -f dangling=true)

docker images 支持更多的过滤器语法,比如,希望看到 ubuntu:14.04 之后建立的镜像,可以

sudo docker images -f since=ubuntu:14.04

想看某个时间之前的镜像,可以把 since 改成 before

也可以自定输出格式

sudo docker images --format ": "
sudo docker images --format "table \t\t"

运行镜像

可以运行本地镜像

sudo docker run -t -i ubuntu:12.04 /bin/bash

sudo docker run --name=webserver –p 8080:80 –d nginx
  • run 命令用来创建一个 docker container
  • 如果有 -p 参数,用来将 -p local-machine-port:internal-container-port 暴露出来,比如 8080:80 将内部的 80 端口映射到 8080 端口

查看当前运行的容器

当运行某一个镜像时,docker 会自动创建一个 container 容器在,该容器中运行该镜像,可以使用 sudo docker ps -a 来查看当前正在运行的容器。可以使用 docker stop [ContainerId] 来终止一个容器的运行。

终止容器

使用 docker stop 来终止一个运行中的容器,当前正在运行或者终止的容器可以使用 sudo docker ps -a 来查看。

处于终止状态的容器,可以通过 docker start 命令来重新启动。

此外,docker restart 命令会将一个运行态的容器终止,然后再重新启动它。

进入容器

但在 docker run 时使用 -d 参数时,容器会进入后台,可以使用 attach 来进入容器

sudo docker attach <containerid>

移除容器和镜像

只有当容器停止运行时才可以将其删除,确认容器已经停止,使用 sudo docker rm [Container Id] 来删除一个容器, 使用 sudo docker rmi [ImageId] 来删除一个本地的镜像。


2017-07-16 Docker , Linux

Spring MVC 处理文件上传

Spring MVC 处理文件上传

添加Maven依赖

<dependency>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
   <version>2.4</version>
</dependency>
<dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>1.3.1</version>
</dependency>

添加界面显示

<form method="POST" action="uploadFile" enctype="multipart/form-data">
	File to upload: <input type="file" name="file"> 
	Name: <input type="text" name="name"> 
	<input type="submit" value="Upload"> Press here to upload the file! 
</form>

form 的enctype 应该是 multipart/form-data

在 WEB-INF 目录下 servlet-context.xml 中添加配置

<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="104857600"/>
</bean>

注意这里的 size 单位是 B,所以上面的大小限制是 100MB = 1024 * 1024 * 100

添加上传逻辑

Controller 中代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/upload")
public class FileController {
	private static Logger logger = LoggerFactory.getLogger(FileController.class);

	@RequestMapping(value = "/zip", method = RequestMethod.POST)
	@ResponseBody
	public CommonResponse<ZipResponse> upload(@RequestParam("file") MultipartFile file,
													HttpServletRequest req,
													HttpServletResponse resp) throws BaseApiException {
		// deal with CSRF
		CommonResponse<ZipResponse> commonResponse = new CommonResponse<>();
		ZipResponse response = new ZipResponse();
		try {
			File cFile = new File("/tmp/" + file.getOriginalFilename());
			file.transferTo(cFile);

			FdsFileService fdsFileService = new FdsFileService();
			// store some where
			String url = "";
			response.setUrl(url);
			commonResponse.setData(response);
			cFile.delete();
		} catch (Exception e) {
			commonResponse.fail("fail");
			logger.error("上传ZIP失败", e);
		}
		return commonResponse;
	}
}

遇到问题

Spring 在处理文件上传的时候报错:

the request was rejected because no multipart boundary was found

解决方法:不要手动设置 Content-Type ,让 Chrome 或者其他浏览器处理, Postman 同理不需要设置 Content-Type。 Refer

reference


2017-07-11 SpringMVC , Java , Web , File

使用 nethogs 查看每个进程流量

在 Linux 上查看系统流量有很多命令,平时一直使用 iftop 来查看单块网卡或者系统整体的流量,iftop 可以查看 TCP 链接的流量情况,分析出流量连往的 IP 地址。但是 iftop 无法做到查看系统中单个进程的网络流量情况。所以 Google 一下之后发现了 nethogs 。

nethogs 自己的介绍

NetHogs is a small ‘net top’ tool. Instead of breaking the traffic down per protocol or per subnet, like most such tools do, it groups bandwidth by process - and does not rely on a special kernel module to be loaded. So if there’s suddenly a lot of network traffic, you can fire up NetHogs and immediately see which PID is causing this, and if it’s some kind of spinning process, kill it.

看介绍就能很快速的知道 nethogs 的设计用途,他就是为了查看单独进程流量情况而被创造出来的。nethogs 是一个小型的 net top 工具,不和大多数工具那样按照每个协议或者子网的速度,而是按照进程进行带宽分组。 nethogs 不需要依赖某个特殊的内核模块,如果发生了网络阻塞,你可以启动 nethogs 立即看到哪个 PID 造成的,这样就可以轻松的找到占用带宽的程序,然后进行相应的内容控制。

Install

在 Debian/Ubuntu 下,sudo apt-get install nethogs 官方源的中 nethogs 因为版本过老,可能会有一些问题,不过可以先尝试安装一下。如果启动 sudo nethogs 之后发现有

creating socket failed while establishing local IP - are you root?

这样的错误。(PS: 这个错误已经在 0.8.1 中被解决)请使用编译安装。

wget -c https://github.com/raboof/nethogs/archive/v0.8.5.tar.gz
tar xf v0.8.5.tar.gz 
cd ./nethogs-0.8.5/

安装必要的依赖,编译安装

sudo apt-get install libncurses5-dev libpcap-dev
make && sudo make install 

检查版本并启动

nethogs -V
sudo nethogs

Usage

使用就非常简单了,直接运行就能查看结果.

~# nethogs
NetHogs version 0.8.5
 
  PID USER     PROGRAM                      DEV        SENT      RECEIVED
2214  root     /usr/lib/apt/methods/http    eth0       4.693     238.631 KB/sec
2051  ubuntu   sshd: ubuntu@pts/1           eth0       3.442       0.310 KB/sec
1120  ubuntu   sshd: ubuntu@pts/0           eth0       0.416       0.035 KB/sec
2213  root     /usr/lib/apt/methods/http    eth0       0.021       0.023 KB/sec
?     root     unknown TCP                             0.000       0.000 KB/sec
 
  TOTAL                                                8.572     239.000 KB/sec

自定义刷新频率

在启动 nethogs 时使用 -d seconds 参数定义刷新频率

nethogs -d 1 # 每秒钟刷新

交互模式

在进入 nethogs 之后,可以使用如下的交互命令:

m: 修改网速单位 r: 按照流量排序 s: 按照发送流量排序 q: 退出

reference


2017-07-09 Linux , nethogs , network

使用 supervisor 管理进程

Supervisor (http://supervisord.org) 是一个用 Python 开发的进程管理工具(client/server),可以很方便的用来启动、重启、关闭进程(不仅仅是 Python 进程)。除了对单个进程的控制,还可以同时启动、关闭多个进程,比如很不幸的服务器出问题导致所有应用程序都被杀死,此时可以用 supervisor 同时启动所有应用程序而不是一个一个地敲命令启动。

安装

Supervisor 可以运行在 Linux、Mac OS X 上。如前所述,supervisor 是 Python 编写的,所以安装起来也很方便,可以直接用 pip :

sudo pip install supervisor

如果是 Ubuntu 系统,还可以使用 apt-get 安装。命令如下

sudo apt-get install supervisor

推荐使用 apt 方式安装,避免后期再配置开机启动脚本。

Supervisor 有两个主要的组成部分:

  • supervisord,运行 Supervisor 时会启动一个进程 supervisord,它负责

    1. 启动所管理的进程,并将所管理的进程作为自己的子进程来启动,而且可以在所管理的进程出现崩溃时自动重启。
    2. 响应客户端命令
    3. 日志输出管理
  • supervisorctl,是命令行管理工具,可以用来执行 stop、start、restart 等命令,对这些子进程进行管理。

手动 supervisord 配置

Supervisor 相当强大,提供了很丰富的功能,不过我们可能只需要用到其中一小部分。安装完成之后,可以编写配置文件,来满足自己的需求。为了方便,我们把配置分成两部分:supervisord(supervisor 是一个 C/S 模型的程序,这是 server 端,对应的有 client 端:supervisorctl)和应用程序(即我们要管理的程序)。

首先来看 supervisord 的配置文件。安装完 supervisor 之后,可以运行echo_supervisord_conf 命令输出默认的配置项,也可以重定向到一个配置文件里(如果是apt安装,则默认配置在 /etc/supervisor/supervisord.conf 下):

echo_supervisord_conf > /etc/supervisord.conf

去除里面大部分注释和“不相关”的部分,我们可以先看这些配置:

[unix_http_server]
file=/tmp/supervisor.sock   ; UNIX socket 文件路径,supervisorctl 会使用
;chmod=0700                 ; socket 文件的 mode,默认是 0700
;chown=nobody:nogroup       ; socket 文件的 owner,格式: uid:gid

;[inet_http_server]         ; HTTP 服务器,提供 web 管理界面
;port=127.0.0.1:9001        ; Web 管理后台运行的 IP 和端口,如果开放到公网,需要注意安全性
;username=user              ; 登录管理后台的用户名
;password=123               ; 登录管理后台的密码

[supervisord]
logfile=/tmp/supervisord.log ; 日志文件,默认是 $CWD/supervisord.log
logfile_maxbytes=50MB        ; 日志文件大小,超出会 rotate (分割),默认 50MB
logfile_backups=10           ; 日志文件保留备份数量默认 10
loglevel=info                ; 日志级别,默认 info,其它: debug,warn,trace
pidfile=/tmp/supervisord.pid ; pid 文件
nodaemon=false               ; 是否在前台启动,默认是 false,即以 daemon 的方式启动
minfds=1024                  ; 可以打开的文件描述符的最小值,默认 1024
minprocs=200                 ; 可以打开的进程数的最小值,默认 200

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; 通过 UNIX socket 连接 supervisord,路径与 unix_http_server 部分的 file 一致
;serverurl=http://127.0.0.1:9001 ; 通过 HTTP 的方式连接 supervisord

; 包含其他的配置文件
[include]
files = relative/directory/*.ini    ; 可以是 *.conf 或 *.ini

我们把上面这部分配置保存到 /etc/supervisord.conf(或其他任意有权限访问的文件),然后启动 supervisord(通过 -c 选项指定配置文件路径,如果不指定会按照这个顺序查找配置文件:$CWD/supervisord.conf, $CWD/etc/supervisord.conf, /etc/supervisord.conf)

supervisord -c /etc/supervisord.conf

查看 supervisord 是否在运行:

ps aux | grep supervisord

supervisord 是 supervisor 的守护进程,但是他自身并没有 reload 选项,因此需要使用

service supervisor restart  # 来重启 supervisord

如果需要使用其他 conf 文件,在 stop supervisord 之后在使用 -c 参数后接配置文件。

APT安装

可以使用 sudo service supervisor status 来查看 supervisord 的服务状态

program 配置

上面我们已经把 supervisrod 运行起来了,现在可以添加我们要管理的进程的配置文件。可以把所有配置项都写到 supervisord.conf 文件里,但并不推荐这样做,而是通过 include 的方式把不同的程序(组)写到不同的配置文件里。

为了举例,我们新建一个目录 /etc/supervisor/ 用于存放这些配置文件,相应的,把 /etc/supervisord.conf 里 include 部分的的配置修改一下:

[include]
files = /etc/supervisor/*.conf

假设有个用 Python 和 Flask 框架编写的用户中心系统,取名 program_name,用 gunicorn (http://gunicorn.org/) 做 web 服务器。项目代码位于 /home/einverne/projects/program_name,gunicorn 配置文件为 gunicorn.py,WSGI callable 是 wsgi.py 里的 app 属性。所以直接在命令行启动的方式可能是这样的:

cd /home/einverne/projects/program_name
gunicorn -c gunicorn.py wsgi:app

现在编写一份配置文件来管理这个进程(需要注意:用 supervisord 管理时,gunicorn 的 daemon 选项需要设置为 False):

; 设置进程的名称, 使用 supervisorctl 来管理进程需要使用该进程名
[program:your_program_name]
directory = /home/einverne/projects/name; 程序的启动目录
command = gunicorn -c gunicorn.py wsgi:app  ; 启动命令,与手动在命令行启动的命令是一样的
autostart = true     ; 在 supervisord 启动的时候也自动启动
startsecs = 5        ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true   ; 程序异常退出后自动重启
startretries = 3     ; 启动失败自动重试次数,默认是 3
user = root          ; 用哪个用户启动
redirect_stderr = true  ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB  ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20     ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /data/logs/program_name_stdout.log
loglevel = info      ; loglevel 指定了日志级别, python 的 print 语句输出的日志是不会被记录到日志文件的,需要搭配 Python 的 logging 模块来输出指定级别的日志

; 可以通过 environment 来添加需要的环境变量,一种常见的用法是修改 PYTHONPATH
; environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere

一份配置文件至少需要一个 [program:x] 部分的配置,来告诉 supervisord 需要管理那个进程。[program:x] 语法中的 x 表示 program name,会在客户端(supervisorctl 或 web 界面)显示,在 supervisorctl 中通过这个值来对程序进行 start、restart、stop 等操作。更加详细的配置可以参考官网

配置一组程序

使用 group 开启或者关闭一组程序,在配置目录下加上额外的配置文件

[group:group1] 
programs=group-member-1,group-member-2   ; each refers to 'x' in [program:x] definitions
priority=999                  ; the relative start priority (default 999)

[program:group-member-1] 
command=xxx 
autostart=true 
autorestart=true 
user=redis 
stdout_logfile=xxx 
stderr_logfile=xxx

[program:group-member-2]
command=xxx 
autostart=true 
autorestart=true 
user=redis 
stdout_logfile=xxx 
stderr_logfile=xxx

添加了 group 配置之后, 进程管理名就变成了 group1:group-member-1 这样的形式,可以使用如下方法启动一组程序

supervisor> start group1:*

使用 supervisorctl

Supervisorctl 是 supervisord 的一个命令行客户端工具,启动时需要指定与 supervisord 使用同一份配置文件,否则与 supervisord 一样按照顺序查找配置文件。

supervisorctl -c /etc/supervisord.conf

上面这个命令会进入 supervisorctl 的 shell 界面,然后可以执行不同的命令了:

> status    # 查看程序状态
> stop program_name   # 关闭 program_name 程序
> start program_name  # 启动 program_name 程序
> restart program_name    # 重启 program_name 程序
> reread    # 读取有更新(增加)的配置文件,不会启动新添加的程序,也不会重启任何程序
> reload    #  载入最新的配置文件,停止原有的进程并按照新的配置启动
> update    # 重启配置文件修改过的程序,配置没有改动的进程不会收到影响而重启

上面这些命令都有相应的输出,除了进入 supervisorctl 的 shell 界面,也可以直接在 bash 终端运行:

$ supervisorctl status
$ supervisorctl stop program_name
$ supervisorctl start program_name
$ supervisorctl restart program_name
$ supervisorctl reread
$ supervisorctl reload
$ supervisorctl update

日志管理

当 supervisor 的日志文件大小超过 stdout_logfile_maxbytes 时,之前的日志文件会被放到 logfile.log.1 文件中备份。可以在相应program配置中配置如下两项改变日志的行为:

  • 配置 stdout_logfile_maxbytes 为0 时,所有的日志文件都会被放到一个文件中
  • 配置 stdout_logfile_backups 为0 时,当日志文件太大时,旧文件就会被删除而不是移动到单独的文件中。

配置 stderr_logfile_maxbytesstderr_logfile_backups 类似。

这样的日志方式叫做 log file rotation

其它

除了 supervisorctl 之外,还可以配置 supervisrod 启动 web 管理界面,这个 web 后台使用 Basic Auth 的方式进行身份认证。

除了单个进程的控制,还可以配置 group,进行分组管理。

经常查看日志文件,包括 supervisord 的日志和各个 pragram 的日志文件,程序 crash 或抛出异常的信息一半会输出到 stderr,可以查看相应的日志文件来查找问题。

Supervisor 有很丰富的功能,还有其他很多项配置,可以在官方文档获取更多信息:http://supervisord.org/index.html

一些问题

开机启动 supervisor

在使用 pip 安装的时候默认并没有安装成服务,因此如果想要使用开机启动可以使用 APT 安装。 而如果已经安装了 supervisor 想要自己配置开机启动脚本,可以使用这个 link 中的方法来添加。

supervisor 官方提供 的开机脚本似乎对于 Ubuntu 有些问题,可以使用上方 Serverfault 中提到的。

sudo curl https://gist.github.com/howthebodyworks/176149/raw/88d0d68c4af22a7474ad1d011659ea2d27e35b8d/supervisord.sh > /etc/init.d/supervisord
sudo chmod +x /etc/init.d/supervisord
sudo update-rc.d supervisord defaults

确保在 /etc/supervisord.conf 中配置了正确的 pid, 并且和 /etc/init.d/supervisord 相对应:

pidfile=/var/run/supervisord.pid

测试:

service supervisord stop
service supervisord start

某些情况下通过 supervisor 启动 program 会报错

错误关键字:supervisor can’t find command

这时候可以手动 开启一个 bash ,或者 sh

commmand=sh -c 'your command'

启动 supervisord 权限问题

问题关键字:error: <class ‘socket.error’>, [Errno 13] Permission denied: file: /usr/lib/python2.7/socket.py line

权限问题,绝大部分情况下使用 sudo supervisorctl 即可解决。当然如果你愿意配置一个 supervisor 用户组,然后在 配置文件中配置相应的权限也可以解决。

具体参考:https://github.com/Supervisor/supervisor/issues/173

reference


2017-07-08 supervisor , python

Google+

最近文章

  • vim presentation 大纲
  • Vim 寄存器 Vim 的寄存器可以看成 Vim 中额外用来存储信息的区域,虽然看不见,但是如果使用 x, s, y, p 等等命令的时候都无意识的使用到了 Vim 的寄存器(register).
  • vim normal 命令 替换::%s/^/#/g visual block:ggI# 注释第一行后用.重复执行每一行 我们可以在第三种方法之上用normal命令实现上述需求,步骤:
  • Vim 中的宏命令 Vim 的设计哲学中有这样一句话:”if you write a thing once, it is okay. However if you’re writing it twice or more times, then you should find a better way to do it”.
  • headless chrome puppeteer Headless 最早的时候在 PhantomJS 听说过这个概念,后来在 GitHub 各种项目中总有人不断提起这个概念,而最新看到的新闻便是 Chrome 开始支持 Headless,也正激起了我了解的欲望。