使用 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

MySQL 数据类型

了解并熟悉 MySQL 中的数据类型,对建表和数据库优化都非常重要。 MySQL 实现了 SQL 定义的类型,也响应的增加乐意 tiny, small, big 的类型。 MySQL 的数据类型主要分成三个部分:

  • Numeric Type 数值型
  • Date and Time Type 日期和时间
  • String Type 字符型

更多的内容可以在官网 查到。

整型

MySQL数据类型 大小 范围(有符号)
TINYINT(m) 1字节 范围(-128~127)
SMALLINT(m) 2个字节  范围(-32768~32767)
MEDIUMINT(m) 3个字节 范围(-8388608~8388607)
INT(m) 4个字节 范围 2^31-1(-2147483648~2147483647)
BIGINT(m) 8个字节  范围 2^63-1(+-9.22*10的18次方)

取值范围如果加了unsigned (无符号),则最大值翻倍,如 TINYINT unsigned 的取值范围为(0~256)。 INT(m)里的m是表示SELECT查询结果集中的显示宽度,并不影响实际的取值范围,没有影响到显示的宽度,不知道这个m有什么用。

浮点型(float和double)

MySQL数据类型 大小 含义
float(m,d) 4字节 单精度浮点型    8位精度(4字节)        m总个数,d小数位
double(m,d) 8字节 双精度浮点型    16位精度(8字节)       m总个数,d小数位

设一个字段定义为float(5,3),如果插入一个数123.45678,实际数据库里存的是123.457,但总个数还以实际为准,即6位。

定点数

浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值。 decimal(m,d) 参数m<65 是总个数,d<30且 d<m 是小数位。

字符串(char,varchar,text)

MySQL数据类型 大小 含义
char(n) 0-255字节 固定长度,最多255个字符
varchar(n) 0-65535 字节 固定长度,最多65535个字符
tinytext 0-255字节 可变长度,最多255个字符
text 0-65 535字节 可变长度,最多65535个字符
mediumtext 0-16 777 215字节 可变长度,最多2的24次方-1个字符
longtext 0-4 294 967 295字节 可变长度,最多2的32次方-1个字符

char和varchar:

  1. char(n) 若存入字符数小于n,则以空格补于其后,查询之时再将空格去掉。所以char类型存储的字符串末尾不能有空格,varchar不限于此。
  2. char(n) 固定长度,char(4)不管是存入几个字符,都将占用4个字节,varchar是存入的实际字符数+1个字节(n<=255)或2个字节(n>255),所以varchar(4),存入3个字符将占用4个字节。
  3. char类型的字符串检索速度要比varchar类型的快。

varchar和text:

  1. varchar可指定n,text不能指定,内部存储 varchar 是存入的实际字符数+1个字节(n<=255)或2个字节(n>255),text 是实际字符数+2个字节。
  2. text类型不能有默认值。
  3. varchar可直接创建索引,text创建索引要指定前多少个字符。varchar查询速度快于text,在都创建索引的情况下,text的索引似乎不起作用。

二进制数据(Blob)

  1. BLOB和 TEXT 存储方式不同,TEXT以文本方式存储,英文存储区分大小写,而Blob是以二进制方式存储,不分大小写。
  2. BLOB存储的数据只能整体读出。
  3. TEXT可以指定字符集,BLOB不用指定字符集。
数据类型 大小 用途
TINYBLOB 0~255字节 不超过 255 个字符二进制字符串
BLOB 0~65535 字节 二进制
MEDIUMBLOB 0-16 777 215字节 二进制形式的中等长度文本数据
LONGBLOB 0-4 294 967 295字节 二进制形式的极大文本数据

日期和时间类型

MySQL数据类型 大小 范围 含义
date 3字节 1000-01-01/9999-12-31 日期 ‘2008-12-2’
time 3字节 ‘-838:59:59’/’838:59:59’ 时间 ‘12:25:36’
datetime 8字节 1000-01-01 00:00:00/9999-12-31 23:59:59 日期时间 ‘2008-12-2 22:06:44’
timestamp 4字节 1970-01-01 00:00:00/2037 年某时 自动存储记录修改时间

每个时间类型有一个有效值范围和一个”零”值,当指定不合法的MySQL不能表示的值时使用”零”值。 若定义一个字段为timestamp,这个字段里的时间数据会随其他字段修改的时候自动刷新,所以这个数据类型的字段可以存放这条记录最后被修改的时间。

数据类型的属性

MySQL关键字 含义
NULL 数据列可包含NULL值
NOT NULL 数据列不允许包含NULL值
DEFAULT 默认值
PRIMARY KEY 主键
AUTO_INCREMENT 自动递增,适用于整数类型
UNSIGNED 无符号
CHARACTER SET name 指定一个字符集

2017-07-07 linux , mysql , sql

知乎上被删除的良心回答之油猴脚本

今天偶然间看到一个知乎问题:“最良心的软件可以良心到什么程度?”,当时在 Google+ 上点进去粗略看了一样,看到油猴脚本也就坦然了,而添加到稍后阅读,在晚上回来之后准备细看时却惊讶于知乎屏蔽的速度,在尝试使用 Google,bing 和 web archive ,baidu 的历史记录之后终于找回了一些渣滓。

我在很早就已经推荐过 Tampermonkey, 也借此推荐过 我用过的 Userscript 。 而 Tampermonkey 我也用了很多年也曾总结过 Tampermonkey 同步的功能。不过多少年过去了,很多脚本失效的失效,我自己之前写得找电影脚本也因为跨域问题,一直懒没有修复。

幸好看到这样一篇总结帖,看看这两年又更新出来多少新玩法。以下为从历史记录中搜出:

直接来看 油猴脚本能干啥:

  • 直接观看各大视频网站的VIP 视频
  • 去除各大视频广告,百度搜索广告
  • 免费收听网易云320k 高音质音乐
  • 高速下载百度云
  • 去除各种验证码

在国内互联网广告满天飞的现在,这些功能无疑会吸引很多的用户。

下面列举一些非常好用的脚本,我在 http://einverne.github.io/post/2015/08/userscripts.html 中列出的脚本不在更新。

网易云高音质下载

在网页上听网易高品质音乐,在网页歌曲页面播放按钮上面也能看到下载歌曲按钮,或者下载歌词,封面,MV 等等。

地址:https://greasyfork.org/zh-CN/scripts/23222

百度网盘直接下载助手

这个插件,你可以使用它可以直接获取文件原始链接,这样你就可以使用第三方(IDM,Folx)下载了,再也不用使用百度云客户端的龟速了。 还可以多个文件选择,批量下载。

地址:https://greasyfork.org/zh-CN/scripts/23635

解决百度云大文件下载限制 https://greasyfork.org/zh-CN/scripts/17800

百度网盘的下载助手,一直在有效,失效,修复,有效,再失效的过程中,因为在 Linux 下很久不用百度云的客户端,而百度最近频频限制下载速度,并且在分享页面增加很多限制,只能使用客户端下载,这么多动作的背后,一方面是因为带宽和存储费用逐渐增高,却无奈找不到任何赢利点,另一方面也因为监管力度的加强。在网盘大战落下帷幕的时候市场上还能够坚持到最后的也就剩下百度,金山,乐视云,360,大大小小的众网盘纷纷宣布停止运营。百度在一家独大之后也是频频限制免费用户的行为,所以用这么多的脚本来提高百度的体验,还不如彻底的原理百度云盘。用自己的一点钱买一个 Dropbox 或者 Google Drive 反而要轻松很多呢。

一键离线下载

将网页上的磁力链接离线到网盘,在寻找电影的时候,这也是经常的动作,这个脚本将找电影,然后到离线的过程自动化了。

https://greasyfork.org/zh-CN/scripts/22590

免费看VIP 视频

直接在 greasyfork.org 搜索 VIP 即可。


2017-06-09 Chrome , userscript , tampermonkey

Ubuntu/Debian 安装 nginx

Nginx 是非常流行的 HTTP/HTTPS 服务器软件,它也可以作为反向代理服务器,邮件代理服务器,可以用于负载均衡,缓存等等。

Nginx 有两个主要的分支可供安装,stable 和 mainline 。这两个分支的主要区别可以从下图看出:

nginx two branchs

stable 分支并不意味着比 mainline 更加稳定可靠,事实上 mainline 更加稳定,因为 Nginx 开发人员会把所有的 bugfixes 都提交到该分支,而只会把 major bugfixes 提交到 stable 分支。然而另一方面,在 stable 分支的提交很少会影响到第三方模块,而在 mainline 上面的开发可能更快所有的新特性,更新,bugs,都会可能对第三方模块造成影响。

Nginx 官方 建议可以在任何时候使用 mainline 分支。而在生产环境使用 stable 分支。

installation

Use following command to install:

sudo apt-get install nginx
nginx -V

all config file is under /etc/nginx/nginx.conf

all vhost is under /etc/nginx/sites-available

program file is under /usr/sbin/nginx

log file is under /var/log/nginx , name of log file is access.log and error.log

init script has been created under /etc/init.d/

start from nginx 1.4.1, the default vhost direcotory is under /usr/share/nginx/html/

apt-get install nginx the config file is under /etc/nginx/site-available/default/

user data can be found in conf file.

sudo nginx -t to test and print log.

manage nginx

start nginx

sudo service nginx start

stop nginx

sudo service nginx stop

other parameters:

reload        restart       start         status        stop

nginx files and path

content

/usr/share/nginx/html/: actual web content, this path can be changed by altering Nginx configuration file.

默认 Ubuntu 16.04 会将 nginx 托管的地址指向 /var/www/html/ 目录。

server configuration

/etc/nginx: The nginx configuration directory. All of the configuration files reside here.

/etc/nginx/sites-available/: The directory where per-site “server blocks” can be stored. Nginx will not use the configuration files found in this directory unless they are linked to the sites-enabled directory (see below). Typically, all server block configuration is done in this directory, and then enabled by linking to the other directory.

/etc/nginx/sites-enabled/: The directory where enabled per-site “server blocks” are stored. Typically, these are created by linking to configuration files found in the sites-available directory.

log

/var/log/nginx/access.log: Every request to your web server is recorded in this log file unless Nginx is configured to do otherwise.

/var/log/nginx/error.log: Any Nginx errors will be recorded in this log.

nginx conf

nginx conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 768;
    # multi_accept on;
}

user
Defines which Linux user will own and run the nginx. Most Debian-based distributions use www-data.

worker_process
Defines how many threads, or simultaneous instances, of nginx to run. Learn more here

pid
Defines where nginx will write its master process ID, or PID.

设置 Nginx Server Blocks

Server Blocks 类似 Apache Virtual Hosts 概念,作用就是通过配置让同一台机器同时托管多个域名。

首先创建目录

sudo mkdir -p /var/www/www.einverne.info/html
sudo chmod -R 755 /var/www/

如果组和用户不是 www-data ,可以用 sudo chown -R www-data:www-data /var/www/www.einverne.info/html 来改变

默认情况下 nginx 包含一个 server block —- default , 创建其他 server block的时候可以以它作为模板

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/www.einverne.info

然后修改该配置

server {
    listen 80;
    listen [::]:80;

    root /var/www/www.einverne.info/html;
    index index.html index.htm index.nginx-debian.html;

    server_name www.einverne.info;

    location / {
        try_files $uri $uri/ =404;
    }
}

修改 vim /etc/nginx/nginx.conf

http {
    . . .

    server_names_hash_bucket_size 64;

    . . .
}

最后需要 ln 启用新的虚拟主机

sudo ln -s /etc/nginx/sites-available/www.einverne.info /etc/nginx/sites-enabled/

使用 sudo nginx -t 来测试配置。

重启 sudo /etc/init.d/nginx reload 启用新配置。

更多的 Nginx 配置相关内容可以查看新文章 Nginx conf

从源代码编译 Nginx 安装

获取 Nginx 最新版本 http://nginx.org/en/download.html 使用最新 mainline 版本即可

下载最新源代码,解压

wget http://nginx.org/download/nginx-1.13.6.tar.gz && tar zxvf nginx-1.13.6.tar.gz

下载安装依赖

以下内容都使用 root 安装 su -

apt-get install -y gcc g++ make automake build-essential

安装 PCRE 库,Nginx Core 和 Rewrite 模块提供正则支持

apt-get install libpcre3 libpcre3-dev

OpenSSL

sudo apt-get install openssl libssl-dev libperl-dev

zlib 库,提供 Gzip 模块支持,压缩headers

apt-get install -y zlib1g zlib1g-dev 

XML xslt

apt-get install libxslt-dev

GD Library

apt-get install libgd2-dev

GeoIP Library

apt-get install libgeoip-dev

使用 APT 源安装 Nginx,并查看版本 nginx -V

nginx -V
nginx version: nginx/1.10.3 (Ubuntu)
built with OpenSSL 1.0.2g  1 Mar 2016
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads

Configure 后面的参数在编译时会需要用到

在反向代理中替换原网页内容,需要在编译时加入第三方模块 substitution

git clone https://github.com/yaoweibin/ngx_http_substitutions_filter_module

另一个方便快捷配置 Google 反代的模块

git clone https://github.com/cuber/ngx_http_google_filter_module

然后进入 Nginx 源代码目录

cd nginx-1.13.6/
./configure \
> --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads \
> --add-module=../ngx_http_substitutions_filter_module \
> --add-module=../ngx_http_google_filter_module

设置后,开始检查编译参数和环境,如果少了某些安装包,或者需要特定版本的lib就会报错,Google 一下需要的依赖包安装即可。安装之后再次 ./configure

检查通过显示

Configuration summary
  + using threads
  + using system PCRE library
  + using system OpenSSL library
  + using system zlib library

  nginx path prefix: "/usr/share/nginx"
  nginx binary file: "/usr/sbin/nginx"
  nginx modules path: "/usr/share/nginx/modules"
  nginx configuration prefix: "/etc/nginx"
  nginx configuration file: "/etc/nginx/nginx.conf"
  nginx pid file: "/run/nginx.pid"
  nginx error log file: "/var/log/nginx/error.log"
  nginx http access log file: "/var/log/nginx/access.log"
  nginx http client request body temporary files: "/var/lib/nginx/body"
  nginx http proxy temporary files: "/var/lib/nginx/proxy"
  nginx http fastcgi temporary files: "/var/lib/nginx/fastcgi"
  nginx http uwsgi temporary files: "/var/lib/nginx/uwsgi"
  nginx http scgi temporary files: "/var/lib/nginx/scgi"

./configure: warning: the "--with-ipv6" option is deprecated

然后编译

make
make install

然后将编译后的文件替换到发行版的安装目录

cp -rf objs/nginx /usr/sbin/nginx

检查 nginx -V 即可看到新编译的版本。

常用配置

Options Explanation
--prefix=<path> 安装的根目录,默认为 /usr/local/nginx
--sbin-path=<path> nginx 二进制文件路劲,如果没有设定,则使用 prefix 作为相对路劲
--conf-path=<path> 配置路径
--error-log-path=<path> 错误log
--pid-path=<path> nginx 写pid文件,通常在 /var/run
--lock-path=<path> 共享内存锁文件
--user=<user> 在哪个用户下运行 worker processes
--group=<group>
--with-debug 开启debug log 生产环境不要启用
--with-http_ssl_module 开启 HTTP SSL 模块,支持 HTTPS
--with-http_realip_module 开启真实来源IP
--with-http_flv_module 开启 flash 视频流
--with-http_mp4_module 开启 H.264/AAC 文件视频流
--with-http_gzip_static_module 开启预压缩文件传前检查,防止文件被重复压缩
--with-http_gunzip_module 开启为不支持 gzip 的客户端提前解压内容
--with-http_stub_status_module 开启 nginx 运行状态
--with_http_substitutions_filter_module 开启替换原网页内容

reference


2017-06-06 Linux , Nginx , Ubuntu , Debian , Web

Celery 最佳实践

尽量不要使用数据库作为 AMQP Broker

随着worker 的不断增多可能给数据库IO和连接造成很大压力。更具体来说不要把 Celery 的task数据和应用数据放到同一个数据库中。 Docker 上很多 相关的镜像。

使用多个队列

对于不同的 task ,尽量使用不同的队列来处理。

@app.task()
def my_taskA(a, b, c):
	print("doing something here...")
@app.task()
def my_taskB(x, y):
	print("doing something here...")

celery_config.py 中定义

task_queues=(
    Queue('default', routing_key='default'),
    Queue('other', routing_key='other'),

在 task 上定义

@app.task(queue='other')
def parse_something():
	pass

定义具有优先级的 workers

假如有一个 taskA 去处理一个队列 A 中的信息,一个 taskB 去处理队列 B 中的数据,然后起了 x 个 worker 去处理队列 A ,其他的 worker 去处理队列 B。而这时也可能会出现队列 B 中一些 task 急需处理,而此时堆积在队列 B 中的 tasks 很多,需要耗费很长时间来处理队列 B 中的 task。此时就需要定义优先队列来处理紧急的task。

celery 中可以在定义 Queue 时,指定 routing_key

Queue('other', routing_key='other_high'),
Queue('other', routing_key='other_low'),

然后定义

task_routes={
	# see
	# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_routes
	# http://docs.celeryproject.org/en/latest/userguide/routing.html#routing-basics
	'path.to.task' : {
		'queue': 'other',
		'routing_key': 'other_high'
	},
	'path.to.task' : {
		'queue': 'other',
		'routing_key': 'other_low'
	},
}

在启动 worker 时指定 routing_key

celery worker -E -l INFO -n workerA -Q other_high
celery worker -E -l INFO -n workerB -Q other_low

使用 celery 的错误处理机制

一般情况下可能因为网络问题,或者第三方服务暂时性错误而导致 task 执行出错。这时可以使用 celery task 的重试机制。

@app.task(bind=True, default_retry_delay=300, max_retries=5)
def my_task_A():
	try:
		print("doing stuff here...")
	except SomeNetworkException as e:
		print("maybe do some clenup here....")
		self.retry(e)

一般添加 default_retry_delay 重试等待时间和 max_retries 重试次数来限定,防止任务无限重试。

使用Flower

Flower 项目 为监控 celery tasks 和 workers 提供了一系列的便利。他使用 Web 界面提供 worker 当前状态, task 执行进度,各个worker 详细信息,甚至可以在网页上动态更行执行速率。

只有在真正需要时才去追踪 celery 的 result

任务的状态存储任务在退出时成功或者失败的信息,这些信息有些时候很重要,尤其是在后期分析数据时,但是大部分情况下更加关心task执行过程中真正想要保存的数据,而不是任务的状态。

所以,可以使用 task_ignore_result = True 来忽略任务结果。

不要将 Database/ORM 对象传入 tasks

不应该讲 Database objects 比如一个 User Model 传入在后台执行的任务,因为这些 object 可能包含过期的数据。相反应该传入一个 user id ,让 task 在执行过程中向数据库请求全新的 User Object。

以上七条来自:https://denibertovic.com/posts/celery-best-practices/

尽量简化 tasks

task 应该简洁(concise):

  • 将主要task逻辑包含在对象方法或者方法中
  • 确保方法抛出明确的异常 (identified exceptions)
  • 只有在切当的时机再实现重试机制

假设需要实现一个发送邮件的task

import requests
from myproject.tasks import app  # app is your celery application
from myproject.exceptions import InvalidUserInput
from utils.mail import api_send_mail
@app.task(bind=True, max_retries=3)
def send_mail(self, recipients, sender_email, subject, body):
	"""Send a plaintext email with argument subject, sender and body to a list of recipients."""
	try:
		data = api_send_mail(recipients, sender_email, subject, body)
	except InvalidUserInput:
		# No need to retry as the user provided an invalid input
		raise
	except Exception as exc:
		# Any other exception. Log the exception to sentry and retry in 10s.
		sentrycli.captureException()
		self.retry(countdown=10, exc=exc)
	return data

通常任务真实的实现只有一层,而剩余的其他部分都是错误处理。而通常这么处理会更加容易维护。

设置task 超时

设置一个全局的任务超时时间

task_soft_time_limit = 600   # 600 seconds

超时之后会抛出 SoftTimeLimitExceeded 异常

from celery.exceptions import SoftTimeLimitExceeded
@app.task
def mytask():
	try:
		return do_work()
	except SoftTimeLimitExceeded:
		cleanup_in_a_hurry()

同样,定义任务时也能够指定超时时间,如果任务block尽快让其失败,尽量配置 task 的超时时间。不让长时间 block task 的进程。

@app.task(
	bind=True,
	max_retries=3,
	soft_time_limit=5 # time limit is in seconds.
)
def send_mail(self, recipients, sender_email, subject, body):
	...

将task重复部分抽象出来

使用task的基类来复用部分 task 逻辑

from myproject.tasks import app
class BaseTask(app.Task):
	"""Abstract base class for all tasks in my app."""
	abstract = True
	def on_retry(self, exc, task_id, args, kwargs, einfo):
		"""Log the exceptions to sentry at retry."""
		sentrycli.captureException(exc)
		super(BaseTask, self).on_retry(exc, task_id, args, kwargs, einfo)
	def on_failure(self, exc, task_id, args, kwargs, einfo):
		"""Log the exceptions to sentry."""
		sentrycli.captureException(exc)
		super(BaseTask, self).on_failure(exc, task_id, args, kwargs, einfo)

@app.task(
	bind=True,
	max_retries=3,
	soft_time_limit=5,
	base=BaseTask)
def send_mail(self, recipients, sender_email, subject, body):
	"""Send a plaintext email with argument subject, sender and body to a list of recipients."""
	try:
		data = api_send_mail(recipients, sender_email, subject, body)
	except InvalidUserInput:
		raise
	except Exception as exc:
		self.retry(countdown=backoff(self.request.retries), exc=exc)
	return data

将大型task作为类

一般情况下将使用方法作为 task 就已经足够,如果遇到大型 task ,可以将其写成

class handle_event(BaseTask):   # BaseTask inherits from app.Task
	def validate_input(self, event):
		...
	def get_or_create_model(self, event):
		...
	def stream_event(self, event):
		...
	def run(self, event):
		if not self.validate_intput(event):
			raise InvalidInput(event)
		try:
			model = self.get_or_create_model(event)
			self.call_hooks(event)
			self.persist_model(event)
		except Exception as exc:
			self.retry(countdown=backoff(self.request.retries), exc=exc)
		else:
			self.stream_event(event)

单元测试

直接调用 worker task 中的方法,不要使用 task.delay() 。 或者使用 Eager Mode,使用 task_always_eager 设置来启用,当启用该选项之后,task会立即被调用。而 这两种方式都只能测试task worker 中的内容,官方1并不建议这么做。

对于执行时间长短不一的任务建议开启 -Ofair

celery 中默认都会有 prefork pool 会异步将尽量多的任务发送给 worker 执行,这也意味着 worker 会预加载一些任务。这对于通常的任务会有性能提升,但这也容易导致因为某一个长任务处理时间长二导致其他任务处于长时间等待状态。

对于执行时间长短不一的任务可以开启 -Ofair

celery -A proj worker -l info -Ofair

设置worker的数量

Celery 默认会开启和CPU core一样数量的 worker,如果想要不想开启多个 worker ,可以通过启动时指定 --concurrency 选项

--concurrency=1

reference


2017-05-21 celery , python , web , database , redis , queue

Git 使用过程中遇到的小技巧

Git 使用过程中遇到的小技巧,平时没有 commit, merge, branch 用的那么勤快,但是需要时也需要查看一下,因此记录一下,以免忘记。

将其他分之中多次提交合并到master的一次提交

开发中经常使用分支开发,因此不可避免的在开发中向 dev,或者 bugfix 分支进行多次提交,而有些提交可能仅仅为了测试,commit message 也没有认认真真写,所以当开发完成,或者bug修复完成想要合并到 master 分支时,不希望保留中间糟糕的提交信息,有一种方法是使用 merge 的 --squash

而在之前我可能会用 soft reset 掉一些提交,然后重新合并为一次提交,而得知 merge 的 squash 之后,可以轻松将其他分之中的多次提交内容一次性合并到工作区中,然后使用 commit 作为提交。

git merge --squash <branch name>
# after
git commit -s
# then write your commit message

这里是 --squash 的解释:

Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or move the HEAD, nor record $GIT_DIR/MERGE_HEAD to cause the next git commit command to create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).

恢复hard reset 丢失的commit

有的时候会做了一些提交,但经过review或者中途发现变化需要丢弃的时候经常用 git reset 来丢掉一些 commit,一般情况下我都会使用 git reset --soft HEAD~1 来丢掉上一个提交,给自己重新检查一下上一次提交的内容。而有时可能不注意直接 git reset --hard <commit-id> 直接丢弃了好几个提交。等敲完回车才追悔莫及,此时就凸显了 git 的强大之处。其实在 Git 中做过的所有提交记录,都是有保存的,每一次修改 HEAD 的操作都被记录到了本地。

git 有一个命令 git reflog 可以查看所有对 HEAD 的变更操作,使用 reflog 命令找到需要恢复的 commit id 然后使用 git reset --hard <commit-id> 来恢复到那一次提交就可以了。

关联本地分支和远程分支

关联本地分支和远程分支,一般情况下使用 git push 时,直接将本地分支推送到远程同名分支,但是如果新项目不是 clone 远程,或者中途曾经更改了 remote,那么有可能 git 就不知道本地分支对应的远程分支,这时候使用 push 或者 pull 的时候就有可能会出错。

使用

git branch --set-upstream-to=origin/master master 

来将本地 master 分支关联到 origin/master 分支。

或者也可以在 push 时自动关联上

git push -u origin master

删除本地某一次提交

本地做了很多修改,而想要放弃其中某一次提交可以使用 git rebase -i , 对于最后一次提交可以使用 git reset --hard HEAD~1 来撤销

对于之前的提交,如果想要删除,可以使用

git rebase -i HEAD~N

来查看本地前 N 次提交,然后编辑文件删除某一次 commit 即可。更多的信息可以参考 Git book

PS. 不要用来改变已经push 到远端的提交,除非明确的知道想要做的事情,可以使用 force push.


2017-05-14 Git , 经验总结

URL 短域名

逛博客看到别人在讨论短URL的设计实现,然后偶然间发现了 GitHub 曾经推出1过的短域名服务 Git.io

创建短域名

curl -i https://git.io -F "url=https://github.com/einverne"                  
HTTP/1.1 100 Continue

HTTP/1.1 201 Created
Server: Cowboy
Connection: keep-alive
Date: Sun, 14 May 2017 03:05:40 GMT
Status: 201 Created
Content-Type: text/html;charset=utf-8
Location: https://git.io/v97cY
Content-Length: 27
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.312051
X-Node: 09a65813-05e0-40a2-a9bf-6dd88da1cdbc
X-Revision: 392798d237fc1aa5cd55cada10d2945773e741a8
Strict-Transport-Security: max-age=31536000; includeSubDomains
Via: 1.1 vegur

使用短域名 302 跳转

curl -i https://git.io/v97cY

HTTP/1.1 302 Found
Server: Cowboy
Connection: keep-alive
Date: Sun, 14 May 2017 03:06:58 GMT
Status: 302 Found
Content-Type: text/html;charset=utf-8
Location: https://github.com/einverne
Content-Length: 0
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.005605
X-Node: d567f758-ba0e-4e8b-95ba-b6a80730cc20
X-Revision: 392798d237fc1aa5cd55cada10d2945773e741a8
Strict-Transport-Security: max-age=31536000; includeSubDomains
Via: 1.1 vegur

还可以使用 code 参数来指定生成的短链接名字,比如

curl -i https://git.io -F "url=https://github.com/...." -F "code=abcd"

git.io 缩短的域名必须是 github 站相关的域名,其他网站的地址它是不会缩短的。并且每个链接只能被缩短一次,如果第二次再请求会返回和上一次缩短一样的结果。

所以无奈啦,

goo.gl

Google 的短域名服务其实已经用很久了 https://goo.gl/ ,相比来说,有几个好处

  • 在登录状态下生成的短域名能够统计跳转数量
  • 在生成之后也与控制面板可以查看曾经生成的短链接
  • 直接在生成的短域名后加上 .qr 可以查看二维码,比如 https://goo.gl/xEeWKp 添加 qr https://goo.gl/xEeWKp.qr

开源版本

PHP 版本 https://github.com/takashiki/Ourls

一个比较好玩的JS纯前端实现,将跳转信息保存到浏览器 Local Storage 中2, 可以学习一下项目中对本地 Storage 操作的部分3,应该挺有意思。


2017-05-14 Github , Google

目录 /usr/local vs /opt 的区别及 JDK 安装

今天看 JDK 的路径突然发现我在两台机子上,一台装在了 /usr/local/ 目录下,而我自己的 Mint 装在了 /opt/ 目录下。感觉对 Linux 目录结构还需要增加了解,就Google了一下。

/usr/local/opt 目录设计为存放非系统级命令,而 /usr/local 目录一般用来防止管理员通过本地编译安装的程序,比如通过 ./configure; make; make install 等命令安装的程序,该目录的目的就是为了使用户产生的命令不和系统命令产生冲突。

/opt 目录一般用来安装非捆绑的软件程序,每个应用都有其自己的子目录,比如在安装Chrome 之后,Chrome 完整的程序和其资源文件都会存在 /opt/google/chrome 下。

安装JDK 的两种方式

因此在 Linux 下如果手工安装 JDK 7/8 时,可以将安装路径手动指定到 /opt 目录下,方便管理。

安装 JDK 的两种方式,一种是直接通过 apt 包管理来安装

sudo add-apt-repository -y ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer

然后使用 java -version 来验证。

或者直接从官网下载压缩包,将文件内容解压到 /opt/java/ 目录下。然后配置环境变量 JAVA_HOME 指向 Java bin 的目录。

wget http://..../jdk-8u91-linux-x64.tar.gz
tar -zxvf jdk-8u91-linux-x64.tar.gz -C /opt/java/
vim ~/.zshrc # or ~/.bashrc

添加

export JAVA_HOME=/opt/java/jdk1.8.0_91/
export PATH="$PATH:$JAVA_HOME/bin/"

使环境变量生效

source ~/.zshrc

更新提供JDK

运行命令会得到目前系统安装的 JDK 或者 JRE,选择序号确定即可。

$ sudo update-alternatives --config java
There are 3 choices for the alternative java (providing /usr/bin/java).
  Selection    Path                                            Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java   1071      auto mode
  1            /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java   1061      manual mode
  2            /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java   1071      manual mode
  3            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      manual mode
Press enter to keep the current choice[*], or type selection number:

reference


2017-05-13 Linux , FHS , Java

SQLAlchemy session 使用问题

在更改 SQLAlchemy Session 从每次请求都创建到共享同一个 Session 之后遇到了如下问题:

StatementError: (sqlalchemy.exc.InvalidRequestError) Can’t reconnect until invalid transaction is rolled back [SQL: ]

或者是

raised unexpected: OperationalError(“(_mysql_exceptions.OperationalError) (2006, ‘MySQL server has gone away’)”,)

错误是 SQLAlchemy 抛出。原因是你从pool拿的connection 没有以 session.commit 或session.rollback 或者session.close 放回pool里。这时connection的transaction 没有完结(rollback or commit)。 而不知什么原因(recyle了,timeout了)你的connection又死掉了,你的sqlalchemy尝试重新连接。由于transaction还没完结,无法重连。

正确用法是确保session 在使用完成后用 session.close, session.commit 或者 session.rollback 把连接还回pool。

SQLAlchemy 数据库连接池使用

sessions 和 connections 不是相同的东西, session 使用连接来操作数据库,一旦任务完成 session 会将 connection 交还给 pool。

在使用create_engine创建引擎时,如果默认不指定连接池设置的话,一般情况下,SQLAlchemy会使用一个QueuePool绑定在新创建的引擎上。并附上合适的连接池参数。

在以默认的方法create_engine时(如下),就会创建一个带连接池的引擎。

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname')

在这种情况下,当你使用了session后就算显式地调用session.close(),也不能把连接关闭。连接会由QueuePool连接池进行管理并复用。

这种特性在一般情况下并不会有问题,不过当数据库服务器因为一些原因进行了重启的话。最初保持的数据库连接就失效了。随后进行的session.query()等方法就会抛出异常导致程序出错。

如果想禁用SQLAlchemy提供的数据库连接池,只需要在调用create_engine是指定连接池为NullPool,SQLAlchemy就会在执行session.close()后立刻断开数据库连接。当然,如果session对象被析构但是没有被调用session.close(),则数据库连接不会被断开,直到程序终止。

下面的代码就可以避免SQLAlchemy使用连接池:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname', poolclass=NullPool)
Session = sessionmaker(bind=engine)
session = Session()
usr_obj_list = session.query(UsrObj).all()
print usr_obj_list[0].id
session.close()

create_engine()函数和连接池相关的参数有:

  • -pool_recycle, 默认为-1, 推荐设置为7200, 即如果connection空闲了7200秒, 自动重新获取, 以防止connection被db server关闭.
  • -pool_size=5, 连接数大小,默认为5,正式环境该数值太小,需根据实际情况调大
  • -max_overflow=10, 超出pool_size后可允许的最大连接数,默认为10, 这10个连接在使用过后, 不放在pool中, 而是被真正关闭的.
  • -pool_timeout=30, 获取连接的超时阈值, 默认为30秒

直接只用create_engine 时,就会创建一个带连接池的引擎

engine = create_engine(‘postgresql://postgres@127.0.0.1/dbname’)

当使用session后就显示地调用session.close(),也不能把连接关闭,连接由QueuePool连接池管理并复用

引发问题

当数据库重启,最初保持的连接就会失败,随后进行session.query() 就会失败抛出异常mysql 数据 ,interactive_timeout等参数处理连接的空闲时间超过(配置时间),断开

scoped session

想要线程安全时使用 scoped_session() ,文档解释

the scoped_session() function is provided which produces a thread-managed registry of Session objects. It is commonly used in web applications so that a single global variable can be used to safely represent transactional sessions with sets of objects, localized to a single thread.

using transactional=False is one solution, but a better one is to simply rollback(), commit(), or close() the Session when operations are complete - transactional mode (which is called “autocommit=False” in 0.5) has the advantage that a series of select operations will all
share the same isolated transactional context..this can be more or less important depending on the isolation mode in effect and the kind of application.

DBAPI has no implicit “autocommit” mode so there is always a transaction implicitly in progress when queries are made.

This would be a fairly late answer. This is what happens: While using the session, a sqlalchemy Error is raised (anything which would also throw an error when be used as pure SQL: syntax errors, unique constraints, key collisions etc.).

You would have to find this error, wrap it into a try/except-block and perform a session.rollback().

After this you can reinstate your session.

reference

  • http://stackoverflow.com/questions/21738944/how-to-close-a-sqlalchemy-session
  • https://groups.google.com/forum/#!topic/sqlalchemy/qAMe78TV0M0
  • http://stackoverflow.com/questions/29224472/sqlalchemy-connection-pool-and-sessions
  • http://docs.sqlalchemy.org/en/latest/orm/session_basics.html?highlight=session#basics-of-using-a-session
  • https://mofanim.wordpress.com/2013/01/02/sqlalchemy-mysql-has-gone-away/

2017-05-12 Python , SQLAlchemy , MySQL

Google+

最近文章

  • 又一款抓包分析软件 wireshark Wireshark 是一款网络分析工具,也是学习网络协议的工具,原先介绍过的 Charles ,mitmproxy 等HTTP抓包工具,都局限于 HTTP/HTTPS 请求,对于更底层的 TCP/IP,UDP 等协议就无能为力了。Wireshark 可以抓取网卡上的网络包,并实时展示,Wireshark 包括了过滤器,协议显示等等工具。
  • 关于游戏的一些想法 最近国内又火了一个答题游戏,回答正确12题平分多少多少万奖金。先不说他们都抄袭 HQ Trivia 这款App,但我这两天一直在想一个问题,为什么这一类的应用能火起来,难道就是因为最后平分100万,200万,现金带来的刺激吗?我想答案一定不是的,除了最开始的玩家或许还能分到几十块钱,在入场玩家越来越多的情况下,每个人瓜分到的奖励一定是越来越少的。那到底是什么能让一个应用一夜之间火到大江南北?
  • Android 电视盒子可用的应用备份 用盒子也已经很多年了,几年来家里,自己用,也积累了一些常用的应用。这两天又拿到了 T1 盒子,又才想起来整理这样一份单子,这样不用每一次都一遍一遍的尝试了。记得以前 VST ,泰捷视频都还很不错的时候,再后来广电发了禁令,再后来这片市场混乱发展,各家大型网站优酷,爱奇艺又不敢公开大搞,却又在背后偷偷摸摸。再到现在几乎被什么芒果,CIBN垄断,内容没什么可看,却什么都要收费。我始终抱有一个观点,如果电视盒子这一块开放发展,国内的厂家完全能够占领全世界的盒子市场,好几年前用的 Android 盒子就已经能够满足我的大部分需求,并且应用设计也早 Google 自己推出 Android TV 盒子以及规范 Android TV 应用好多年。可惜这一块市场被一道禁令打到了地下。
  • okhttp 使用 OkHttp是一个非常高效的HTTP客户端,默认情况下:
  • 斐讯 T1 盒子去除广告 斐讯投资送硬件这路子是着魔了,所以在斐讯 K2P 之后又入手了这个 T1 的盒子,配置2G RAM,16G Sdcard,配置还不错。接口有 USB,网口,HDMI 口,还有 一个 AV 输出口,日常使用是没有任何问题的,初尝试一下非常流畅,不过让我不爽的是,第一次进入竟然需要验证手机,说是 CIBN 盒子的验证,如果不注册还不让进入。