Laravel Eloquent 使用 groupBy 获取每个group的数据和

最近写到相关的代码,直接上代码做个记录。

public function scopeUserSum($query)
{
    return $query->groupBy('username')->selectRaw('sum(data) as sum, username')->orderBy('sum', 'desc');
}

如果只是纯粹求一列的和,在builder上直接用sum即可。

参考资料


Shell 拉起进程后获得 pid,$0,$?,$!等用法

起因是在前一篇《Linux 网络监控与统计实例》(/tech/2017/04/13/linux-network-monitor-and-stats-example.html)中杀死 tcpdump 的代码在流量监控项目中是有bug的,源码如下:

#tcpdump监听网络
tcpdump -v -i $eth -tnn > /tmp/tcpdump_temp 2>&1 &
sleep 10
clear
kill `ps aux | grep tcpdump | grep -v grep | awk '{print $2}'`

这个代码中最后一句 kill 的结果实质上是杀死正在运行中的 tcpdump。然而如果系统中原先已存在tcpdump,就会造成不可预期的结果。

比较适当的做法是记录运行 tcpdump 时的 pid,最后再 kill 掉。相关的代码其实非常简单:

$! # Shell最后运行的后台Process的PID

所以代码调整后的结果就是

# tcpdump监听网络
tcpdump -v -i $eth -tnn > $tmpfile1 2>&1 &
local pid=$!
sleep 60
kill $pid

除了 $!, Shell 还有下面这些特殊的用法:

变量 说明
$$ Shell本身的PID(ProcessID)
$! Shell最后运行的后台Process的PID
$? 最后运行的命令的结束代码(返回值)
$- 使用Set命令设定的Flag一览
$* 所有参数列表。如”$*“用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。
$@ 所有参数列表。如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。
$# 添加到Shell的参数个数
$0 Shell本身的文件名
$1~$n 添加到Shell的各参数值。$1是第1参数、$2是第2参数…。
!! 上一个运行的命令
n! history中第n个运行的命令

参考资料


supervisor 安装与配置

Supervisor是一个进程控制系统,由python编写。可以很方便的用来启动、重启、关闭进程(不仅仅是 Python 进程)。 它有以下一些特点

  • 可以配置同时启动的进程数,而不需要一个个启动
  • 可以根据程序的退出码来判断是否需要自动重启
  • 可以配置进程初始化的环境,包括目录,用户,umask,关闭进程所需要的信号等等
  • 有web界面进行管理

下面简单介绍一下 supervisor 的安装使用过程。更复杂的 group 等设置可以参考我文末的参考资料。

安装

使用python包管理工具pip进行安装:

pip install supervisor

配置

安装完 supervisor 之后,运行echo_supervisord_conf 命令输出默认的配置项,重定向到一个配置文件里:

echo_supervisord_conf > /etc/supervisord.conf

下面是我经过简化过的配置:

[unix_http_server]
file=/tmp/supervisor.sock   ; (the path to the socket file)

[supervisord]
logfile=/var/local/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB        ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10           ; (num of main logfile rotation backups;default 10)
loglevel=info                ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false               ; (start in foreground if true;default false)
minfds=1024                  ; (min. avail startup file descriptors;default 1024)
minprocs=200                 ; (min. avail process descriptors;default 200)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket

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

然后新建文件夹来存放各种进程的配置文件:

mkdir /etc/supervisor

目前我是用来管理 laravel 的进程,在 /etc/supervisor/ 里添加文件 laravel-wechat.conf

[program:wechat]
directory = /var/local/fpm-pools/wechat/www ; 程序的启动目录
command = php /var/local/fpm-pools/wechat/www/artisan queue:work --sleep=3 --daemon --tries=3
process_name=%(program_name)s_%(process_num)02d
autostart = true     ; 在 supervisord 启动的时候也自动启动
;startsecs = 5        ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true   ; 程序异常退出后自动重启
startretries = 3     ; 启动失败自动重试次数,默认是 3
user = www-data          ; 用哪个用户启动
redirect_stderr = true  ; 把 stderr 重定向到 stdout,默认 false
numprocs = 8
stdout_logfile_maxbytes = 20MB  ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20     ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /var/local/log/wechat/wechat.queue.log

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

启动后命令行界面输入 supervisorctl 进入控制界面,如下则说明 supervisor 启动成功、laravel 进程配置成功

启动 supervisord

# 使用默认的配置文件 /etc/supervisord.conf
supervisord
# 明确指定配置文件
supervisord -c /etc/supervisord.conf
# 使用 user 用户启动 supervisord
supervisord -u user

supervisorctl 命令介绍

# 停止某一个进程,program_name 为 [program:x] 里的 x
supervisorctl stop program_name
# 启动某个进程
supervisorctl start program_name
# 重启某个进程
supervisorctl restart program_name
# 结束所有属于名为 groupworker 这个分组的进程 (start,restart 同理)
supervisorctl stop groupworker:
# 结束 groupworker:name1 这个进程 (start,restart 同理)
supervisorctl stop groupworker:name1
# 停止全部进程,注:start、restart、stop 都不会载入最新的配置文件
supervisorctl stop all
# 载入最新的配置文件,停止原有进程并按新的配置启动、管理所有进程
supervisorctl reload
# 根据最新的配置文件,启动新配置或有改动的进程,配置没有改动的进程不会受影响而重启
supervisorctl update

注意:显示用 stop 停止掉的进程,用 reload 或者 update 都不会自动重启。

开机自动启动 Supervisord

Supervisord 默认情况下并没有被安装成服务,它本身也是一个进程。

# 下载脚本
sudo su - root -c "sudo curl https://gist.githubusercontent.com/howthebodyworks/176149/raw/d60b505a585dda836fadecca8f6b03884153196b/supervisord.sh > /etc/init.d/supervisord"
# 设置该脚本为可以执行
sudo chmod +x /etc/init.d/supervisord
# 设置为开机自动运行
sudo update-rc.d supervisord defaults
# 试一下,是否工作正常
service supervisord stop
service supervisord start 

参考资料


Linux 网络监控与统计实例

最近写到相关的代码,做个记录。

知识补充

首先补充本文用到的 Shell/Linux 的一些知识。

  • 数组
  • grep
  • awk
  • sed
  • ss
  • tcpdump

数组

ArrayName=("element 1" "element 2" "element 3") ## 定义数组
${distro[0]}        ## 访问数组
${#distro[@]}       ## 数组长度

grep

实例:

ifconfig | grep -E -o "^[a-z0-9]+" | grep -v "lo"
ifconfig | grep -A 1 eth0

手册:

grep [OPTIONS] PATTERN [FILE...]
grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

Matcher Selection
   -E, --extended-regexp 正则
General Output Control
    -o, --only-matching 仅匹配非空格
    -v, --invert-match 非匹配
Context Line Control
    -A NUM, --after-context=NUM 打印匹配行之后第几行

awk

以前写过一篇Linux命令之awk,详细内容可以进去查看。这里只涉及需要用到的。

实例:

cat xxx | awk -F'[: ]+' '$0~/inet addr:/{printf $4"|"}
awk -v eth=$eth -F'[: ]+' '{if ($0 ~eth){print $3,$11}}

手册:

awk '/search pattern1/ {Actions}    
     /search pattern2/ {Actions}' file    
     
* search pattern是正则表达式
* Actions 输出的语法
* 在Awk 中可以存在多个正则表达式和多个输出定义
* file 输入文件名
* 单引号的作用是包裹起来防止shell截断

背景概念:

awk默认是以行为单位处理文本的 awk中的两个术语:

    记录(默认就是文本的每一行)
    字段 (默认就是每个记录中由空格或TAB分隔的字符串)

$0就表示一个记录,$1表示记录中的第一个字段。

解释:

-F'[: ]+' 以:或者空格作为分隔符
$0表示一个记录(行)
~ 表示模式开始。/ /中是模式。
/inet addr:/这就是一个正则表达式的匹配
{printf $4"|"} 打印按照分隔符分割所成数组的第四个字段

-v 是将 Shell 变量 $eth 的值传给 awk 变量。

sed

sed在处理文本文件的时候,会在内存上创建一个模式空间,然后把这个文件的每一行调入模式空间用相应的命令处理,处理完输出;接着处理下一行,直到最后。

cat xxx | sed -e 's/|$//' 
cat xxx | sed  -n '1,3p' ## 打印1~3行

手册:

sed [选项]  [定址commands] [inputfile]

关于定址:
定址可以是0个、1个、2个;通知sed去处理文件的哪几行。
0个:没有定址,处理文件的所有行
1个:行号,处理行号所在位置的行
2个:行号、正则表达式,处理被行号或正则表达式包起来的行

/proc/net/dev

Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
  ppp1: 2007461   25873    0    0    0     0          0         0 33320940   30484    0    0    0     0       0          0
    lo: 443577110 1042128    0    0    0     0          0         0 443577110 1042128    0    0    0     0       0          0
  ppp0: 5894011   36473    0    0    0     0          0         0 72146111   64491    0    0    0     0       0          0
docker0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  eth0: 59705148255 56461407    0    0    0     0          0         0 49418615742 41712001    0    0    0     0       0          0

ss

ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。

tcpdump

tcpdump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤。

基本上tcpdump总的的输出格式为:系统时间 来源主机.端口 > 目标主机.端口 数据包参数

-i eth0  # 监视指定网络接口的数据包 

例子

代码如下:下载地址

#!/bin/bash
 
#write by zhumaohai(admin#centos.bz)
#author blog: www.centos.bz
 
 
#显示菜单(单选)
display_menu(){
local soft=$1
local prompt="which ${soft} you'd select: "
eval local arr=(\${${soft}_arr[@]})
while true
do
    echo -e "#################### ${soft} setting ####################\n\n"
    for ((i=1;i<=${#arr[@]};i++ )); do echo -e "$i) ${arr[$i-1]}"; done
    echo
    read -p "${prompt}" $soft
    eval local select=\$$soft
    if [ "$select" == "" ] || [ "${arr[$soft-1]}" == ""  ];then
        prompt="input errors,please input a number: "
    else
        eval $soft=${arr[$soft-1]}
        eval echo "your selection: \$$soft"             
        break
    fi
done
}
 
#把带宽bit单位转换为人类可读单位
bit_to_human_readable(){
    #input bit value
    local trafficValue=$1
 
    if [[ ${trafficValue%.*} -gt 922 ]];then
        #conv to Kb
        trafficValue=`awk -v value=$trafficValue 'BEGIN{printf "%0.1f",value/1024}'`
        if [[ ${trafficValue%.*} -gt 922 ]];then
            #conv to Mb
            trafficValue=`awk -v value=$trafficValue 'BEGIN{printf "%0.1f",value/1024}'`
            echo "${trafficValue}Mb"
        else
            echo "${trafficValue}Kb"
        fi
    else
        echo "${trafficValue}b"
    fi
}
 
#判断包管理工具
check_package_manager(){
    local manager=$1
    local systemPackage=''
    if cat /etc/issue | grep -q -E -i "ubuntu|debian";then
        systemPackage='apt'
    elif cat /etc/issue | grep -q -E -i "centos|red hat|redhat";then
        systemPackage='yum'
    elif cat /proc/version | grep -q -E -i "ubuntu|debian";then
        systemPackage='apt'
    elif cat /proc/version | grep -q -E -i "centos|red hat|redhat";then
        systemPackage='yum'
    else
        echo "unkonw"
    fi
 
    if [ "$manager" == "$systemPackage" ];then
        return 0
    else
        return 1
    fi   
}
 
 
#实时流量
realTimeTraffic(){
    local eth=""
    local nic_arr=(`ifconfig | grep -E -o "^[a-z0-9]+" | grep -v "lo" | uniq`)
    local nicLen=${#nic_arr[@]}
    if [[ $nicLen -eq 0 ]]; then
        echo "sorry,I can not detect any network device,please report this issue to author."
        exit 1
    elif [[ $nicLen -eq 1 ]]; then
        eth=$nic_arr
    else
        display_menu nic
        eth=$nic
    fi   
 
    local clear=true
    local eth_in_peak=0
    local eth_out_peak=0
    local eth_in=0
    local eth_out=0
 
    while true;do
        #移动光标到0:0位置
        printf "\033[0;0H"
        #清屏并打印Now Peak
        [[ $clear == true ]] && printf "\033[2J" && echo "$eth--------Now--------Peak-----------"
        traffic_be=(`awk -v eth=$eth -F'[: ]+' '{if ($0 ~eth){print $3,$11}}' /proc/net/dev`)
        sleep 2
        traffic_af=(`awk -v eth=$eth -F'[: ]+' '{if ($0 ~eth){print $3,$11}}' /proc/net/dev`)
        #计算速率
        eth_in=$(( (${traffic_af[0]}-${traffic_be[0]})*8/2 ))
        eth_out=$(( (${traffic_af[1]}-${traffic_be[1]})*8/2 ))
        #计算流量峰值
        [[ $eth_in -gt $eth_in_peak ]] && eth_in_peak=$eth_in
        [[ $eth_out -gt $eth_out_peak ]] && eth_out_peak=$eth_out
        #移动光标到2:1
        printf "\033[2;1H"
        #清除当前行
        printf "\033[K"   
        printf "%-20s %-20s\n" "Receive:  $(bit_to_human_readable $eth_in)" "$(bit_to_human_readable $eth_in_peak)"
        #清除当前行
        printf "\033[K"
        printf "%-20s %-20s\n" "Transmit: $(bit_to_human_readable $eth_out)" "$(bit_to_human_readable $eth_out_peak)"
        [[ $clear == true ]] && clear=false
    done
}
 
#流量和连接概览
trafficAndConnectionOverview(){
    if ! which tcpdump > /dev/null;then
        echo "tcpdump not found,going to install it."
        if check_package_manager apt;then
            apt-get -y install tcpdump
        elif check_package_manager yum;then
            yum -y install tcpdump
        fi
    fi
 
    local reg=""
    local eth=""
    local nic_arr=(`ifconfig | grep -E -o "^[a-z0-9]+" | grep -v "lo" | uniq`)
    local nicLen=${#nic_arr[@]}
    if [[ $nicLen -eq 0 ]]; then
        echo "sorry,I can not detect any network device,please report this issue to author."
        exit 1
    elif [[ $nicLen -eq 1 ]]; then
        eth=$nic_arr
    else
        display_menu nic
        eth=$nic
    fi
 
    echo "please wait for 10s to generate network data..."
    echo
    #当前流量值
    local traffic_be=(`awk -v eth=$eth -F'[: ]+' '{if ($0 ~eth){print $3,$11}}' /proc/net/dev`)
    #tcpdump监听网络
    tcpdump -v -i $eth -tnn > /tmp/tcpdump_temp 2>&1 &
    sleep 10
    clear
    kill `ps aux | grep tcpdump | grep -v grep | awk '{print $2}'`

    #10s后流量值
    local traffic_af=(`awk -v eth=$eth -F'[: ]+' '{if ($0 ~eth){print $3,$11}}' /proc/net/dev`)
    #打印10s平均速率
    local eth_in=$(( (${traffic_af[0]}-${traffic_be[0]})*8/10 ))
    local eth_out=$(( (${traffic_af[1]}-${traffic_be[1]})*8/10 ))
    echo -e "\033[32mnetwork device $eth average traffic in 10s: \033[0m"
    echo "$eth Receive: $(bit_to_human_readable $eth_in)/s"
    echo "$eth Transmit: $(bit_to_human_readable $eth_out)/s"
    echo

    local regTcpdump=$(ifconfig | grep -A 1 $eth | awk -F'[: ]+' '$0~/inet addr:/{printf $4"|"}' | sed -e 's/|$//' -e 's/^/(/' -e 's/$/)\\\\\.[0-9]+:/')
  
    #新旧版本tcpdump输出格式不一样,分别处理
    if awk '/^IP/{print;exit}' /tmp/tcpdump_temp | grep -q ")$";then
        #处理tcpdump文件
        awk '/^IP/{print;getline;print}' /tmp/tcpdump_temp > /tmp/tcpdump_temp2
    else
        #处理tcpdump文件
        awk '/^IP/{print}' /tmp/tcpdump_temp > /tmp/tcpdump_temp2
        sed -i -r 's#(.*: [0-9]+\))(.*)#\1\n    \2#' /tmp/tcpdump_temp2
    fi
    
    awk '{len=$NF;sub(/\)/,"",len);getline;print $0,len}' /tmp/tcpdump_temp2 > /tmp/tcpdump

    #统计每个端口在10s内的平均流量
    echo -e "\033[32maverage traffic in 10s base on server port: \033[0m"
    awk -F'[ .:]+' -v regTcpdump=$regTcpdump '{if ($0 ~ regTcpdump){line="clients > "$8"."$9"."$10"."$11":"$12}else{line=$2"."$3"."$4"."$5":"$6" > clients"};sum[line]+=$NF*8/10}END{for (line in sum){printf "%s %d\n",line,sum[line]}}' /tmp/tcpdump | \
    sort -k 4 -nr | head -n 10 | while read a b c d;do
        echo "$a $b $c $(bit_to_human_readable $d)/s"
    done
    echo -ne "\033[11A"
    echo -ne "\033[50C"
    echo -e "\033[32maverage traffic in 10s base on client port: \033[0m"
    awk -F'[ .:]+' -v regTcpdump=$regTcpdump '{if ($0 ~ regTcpdump){line=$2"."$3"."$4"."$5":"$6" > server"}else{line="server > "$8"."$9"."$10"."$11":"$12};sum[line]+=$NF*8/10}END{for (line in sum){printf "%s %d\n",line,sum[line]}}' /tmp/tcpdump | \
    sort -k 4 -nr | head -n 10 | while read a b c d;do
            echo -ne "\033[50C"
            echo "$a $b $c $(bit_to_human_readable $d)/s"
    done   
        
    echo

    #统计在10s内占用带宽最大的前10个ip
    echo -e "\033[32mtop 10 ip average traffic in 10s base on server: \033[0m"
    awk -F'[ .:]+' -v regTcpdump=$regTcpdump '{if ($0 ~ regTcpdump){line=$2"."$3"."$4"."$5" > "$8"."$9"."$10"."$11":"$12}else{line=$2"."$3"."$4"."$5":"$6" > "$8"."$9"."$10"."$11};sum[line]+=$NF*8/10}END{for (line in sum){printf "%s %d\n",line,sum[line]}}' /tmp/tcpdump | \
    sort -k 4 -nr | head -n 10 | while read a b c d;do
        echo "$a $b $c $(bit_to_human_readable $d)/s"
    done
    echo -ne "\033[11A"
    echo -ne "\033[50C"
    echo -e "\033[32mtop 10 ip average traffic in 10s base on client: \033[0m"
    awk -F'[ .:]+' -v regTcpdump=$regTcpdump '{if ($0 ~ regTcpdump){line=$2"."$3"."$4"."$5":"$6" > "$8"."$9"."$10"."$11}else{line=$2"."$3"."$4"."$5" > "$8"."$9"."$10"."$11":"$12};sum[line]+=$NF*8/10}END{for (line in sum){printf "%s %d\n",line,sum[line]}}' /tmp/tcpdump | \
    sort -k 4 -nr | head -n 10 | while read a b c d;do
        echo -ne "\033[50C"
        echo "$a $b $c $(bit_to_human_readable $d)/s"
    done 

    echo
    #统计连接状态
    local regSS=$(ifconfig | grep -A 1 $eth | awk -F'[: ]+' '$0~/inet addr:/{printf $4"|"}' | sed -e 's/|$//')
    ss -an | grep -v -E "LISTEN|UNCONN" | grep -E "$regSS" > /tmp/ss
    echo -e "\033[32mconnection state count: \033[0m"
    awk 'NR>1{sum[$(NF-4)]+=1}END{for (state in sum){print state,sum[state]}}' /tmp/ss | sort -k 2 -nr
    echo
    #统计各端口连接状态
    echo -e "\033[32mconnection state count by port base on server: \033[0m"
    awk 'NR>1{sum[$(NF-4),$(NF-1)]+=1}END{for (key in sum){split(key,subkey,SUBSEP);print subkey[1],subkey[2],sum[subkey[1],subkey[2]]}}' /tmp/ss | sort -k 3 -nr | head -n 10   
    echo -ne "\033[11A"
    echo -ne "\033[50C"
    echo -e "\033[32mconnection state count by port base on client: \033[0m"
    awk 'NR>1{sum[$(NF-4),$(NF)]+=1}END{for (key in sum){split(key,subkey,SUBSEP);print subkey[1],subkey[2],sum[subkey[1],subkey[2]]}}' /tmp/ss | sort -k 3 -nr | head -n 10 | awk '{print "\033[50C"$0}'   
    echo   
    #统计端口为80且状态为ESTAB连接数最多的前10个IP
    echo -e "\033[32mtop 10 ip ESTAB state count at port 80: \033[0m"
    cat /tmp/ss | grep ESTAB | awk -F'[: ]+' '{sum[$(NF-2)]+=1}END{for (ip in sum){print ip,sum[ip]}}' | sort -k 2 -nr | head -n 10
    echo
    #统计端口为80且状态为SYN-RECV连接数最多的前10个IP
    echo -e "\033[32mtop 10 ip SYN-RECV state count at port 80: \033[0m"
    cat /tmp/ss | grep -E "$regSS" | grep SYN-RECV | awk -F'[: ]+' '{sum[$(NF-2)]+=1}END{for (ip in sum){print ip,sum[ip]}}' | sort -k 2 -nr | head -n 10
}
 
main(){
    while true; do
        echo -e "1) real time traffic.\n2) traffic and connection overview.\n"
        read -p "please input your select(ie 1): " select
        case  $select in
            1) realTimeTraffic;break;;
            2) trafficAndConnectionOverview;break;;
            *) echo "input error,please input a number.";;
        esac
    done   
}
 
main

获得本机 ip

ifconfig | grep -A 1 $eth | awk -F'[: ]+' '$0~/inet addr:/{printf $4"|"}' | sed -e 's/|$//' -e 's/^/(/' -e 's/$/)\\\\\.[0-9]+:/'

结果:

(103.29.71.237)\\.[0-9]+:#

接口流量/proc/net/dev

awk -F'[: ]+' '{if ($0 ~eth0){print $3,$11}}' /proc/net/dev

tcpdump监听网络

tcpdump -v -i eth0 -tnn > /tmp/tcpdump_temp 2>&1 &

杀死进程

kill `ps aux | grep tcpdump | grep -v grep | awk '{print $2}'`

统计10s内速率

awk -F'[ .:]+' -v regTcpdump=$regTcpdump '{if ($0 ~ regTcpdump){line="clients > "$8"."$9"."$10"."$11":"$12}else{line=$2"."$3"."$4"."$5":"$6" > clients"};sum[line]+=$NF*8/10}END{for (line in sum){printf "%s %d\n",line,sum[line]}}' /tmp/tcpdump | \
sort -k 4 -nr | head -n 10 | while read a b c d;do
    echo "$a $b $c $(bit_to_human_readable $d)/s"
done

这一段太复杂了,看不懂Orz

参考资料


python3 的安装与 virtualenv 建立虚拟环境

就目前来说,所有 Linux 发行版自带的python都是python2,由于python3与2不兼容,所以python3的推进一直不顺利。然而我也不想直接踢掉系统的python2环境,哪天需要的时候不还得搞一遍回来么?好在python官方提供了virtualenv环境,可以无缝使用不同版本间的Python。

最近开始部署 python 应用了。于是记录一下配置 python3 环境的步骤。

编译安装Python3.6

直接选择了最新的python版本: 官网下载

cd /tmp
wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tgz
tar -xzvf Python-3.6.1.tgz
cd Python-3.6.1
mkdir /usr/local/python3.6
./configure --prefix=/usr/local/python3.6
make
make install

然后 python3 就安装好了。这次安装会将pip也一并安装了。

安装 Python2 的 pip

两种方式,一种是源码安装:

cd /tmp
wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py

另一种是 apt 安装(Debian系)。

安装virtualenv

pip install virtualenv

使用virtualenv

创建虚拟环境

cd /var/local
virtualenv -p /usr/local/python3.6/bin/python3.6 xx

指定使用python3.6创建一个项目虚拟环境.

激活虚拟环境

cd xx                    # 切换至项目目录
source ./bin/activate

退出虚拟环境

deactivate      # 退出项目的 virtualenv 虚拟环境.

问题定位

参考资料


PostgreSQL入门

新建了一个节点服务器,打算将主业务迁移到这边。涉及到一些 Shell 命令行的数据库操作。在这里做一个简单的记录。

简介

PostgreSQL是完全由社区驱动的开源项目,由全世界超过1000名贡献者所维护。它提供了单个完整功能的版本,而不像MySQL那样提供了多个不同的社区版、商业版与企业版。PostgreSQL基于自由的BSD/MIT许可,组织可以使用、复制、修改和重新分发代码,只需要提供一个版权声明即可。

以下 PostgreSQL 简称为 pg 。

与 Mysql 的区别

MySQL与pg都是免费、开源、强大、且功能丰富的数据库。他们二者都在某些任务上具有很快的速度。

MySQL通常被认为是针对网站与应用的快速数据库后端,能够进行快速的读取和大量的查询操作,不过在复杂特性与数据完整性检查方面不太尽如人意。MySQL不同存储引擎的行为有较大差别。

pg是针对事务型企业应用的严肃、功能完善的数据库,只有单一存储引擎的完全集成的数据库。你可以通过调整postgresql.conf文件的参数来改进性能,也可以调整查询与事务。

安装

参照我在开源项目 KeluLinuxKit 上的 pg 安装过程。目前为止 Debian 自带的 pg 版本还比较老。在这里我添加了 pg 官方维护的源进行安装。

sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
apt-get -y install wget ca-certificates
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
apt-get update
apt-get -y upgrade
apt-get -y install postgresql-9.4 pgadmin3

安装完成后系统会生成 postgres 用户。默认密码也是 postgres。

添加用户和数据库

登录PostgreSQL控制台:

sudo su - postgres
psql

一些操作:

# 为postgres用户设置一个密码。
\password postgres 

# 创建用户
CREATE USER dbuser WITH PASSWORD 'password';

创建数据库
CREATE DATABASE exampledb OWNER dbuser;

赋予权限
GRANT ALL PRIVILEGES ON DATABASE exampledb to dbuser;

退出控制台
\q

控制台命令

- \h:查看SQL命令的解释,比如\h select。
- \?:查看psql命令列表。
- \l:列出所有数据库。
- \c [database_name]:连接其他数据库。
- \d:列出当前数据库的所有表格。
- \d [table_name]:列出某一张表格的结构。
- \du:列出所有用户。
- \e:打开文本编辑器。
- \conninfo:列出当前数据库和连接的信息

数据库操作

基本的数据库操作,就是使用一般的SQL语言。

# 创建新表 
CREATE TABLE user_tbl(name VARCHAR(20), signup_date DATE);
# 插入数据 
INSERT INTO user_tbl(name, signup_date) VALUES('张三', '2013-12-22');
# 选择记录 
SELECT * FROM user_tbl;
# 更新数据 
UPDATE user_tbl set name = '李四' WHERE name = '张三';
# 删除记录 
DELETE FROM user_tbl WHERE name = '李四' ;
# 添加栏位 
ALTER TABLE user_tbl ADD email VARCHAR(40);
# 更新结构 
ALTER TABLE user_tbl ALTER COLUMN signup_date SET NOT NULL;
# 更名栏位 
ALTER TABLE user_tbl RENAME COLUMN signup_date TO signup;
# 删除栏位 
ALTER TABLE user_tbl DROP COLUMN email;
# 表格更名 
ALTER TABLE user_tbl RENAME TO backup_tbl;
# 删除表格 
DROP TABLE IF EXISTS backup_tbl;

数据库备份还原 pg_dump

备份是维护不可或缺的一部分。 pg_dump 是一个用于备份 PostgreSQL 数据库的工具。它甚至可以在数据库正在并发使用的时候进行完整一致的备份。 pg_dump 不阻塞其它用户对数据库的访问(读或者写)。

可以查看官方文档:http://www.postgresql.org/docs/9.4/static/app-pgdump.html

命令示例:

dt=$(date +%Y%m%d%H%M)
pg_dump -s -F c -Z 9 -d db1 > /var/local/pg_dump/db1.$dt.dump // 导出结构
pg_dump -a -F c -Z 9 -d db1 > /var/local/pg_dump/db1.$dt.dump // 只备份数据
pg_dump -F c -Z 9 -d db2 > /var/local/pg_dump/db2.all.dump // 全部备份

pg_restore 是用于恢复由 pg_dump 创建的任何非纯文本输出格式中的 PostgreSQL 数据库的工具。 它将发出必要的命令来重新构造数据库,以便于把它恢复成保存它的时候的样子。 归档(备份)文件还允许pg_restore 有选择地进行恢复, 甚至在恢复前重新排列条目的顺序。归档的文件设计成可以在不同的硬件体系之间移植。 pg_restore 可以以两种模式操作。如果声明了数据库名字, 那么归档是直接恢复到数据库里。 否则,先创建一个包含重建数据库所必须的 SQL 命令的脚本,并且写入到一个文件或者标准输出。 等效于 pg_dump 输出纯文本格式的时候创建的那种脚本。

命令示例:

pg_restore -d db_dst db_src.dump

用法: pg_dump [选项]… [数据库名字] 用法: pg_dump [选项]… [数据库名字]

一般选项:
  -f, --file=FILENAME         output file or directory name
  -F, --format=c|d|t|p        output file format (custom, directory, tar, plain text)
  -v, --verbose            详细模式
  -Z, --compress=0-9       被压缩格式的压缩级别
--lock-wait-timeout=TIMEOUT 在等待表锁超时后操作失败
  --help                       显示此帮助信息, 然后退出
  --versoin                    输出版本信息, 然后退出
  
控制输出内容选项:
  -a, --data-only          只转储数据,不包括模式
  -b, --blobs              在转储中包括大对象
  -c, --clean              在重新创建之前,先清除(删除)数据库对象
  -C, --create             在转储中包括命令,以便创建数据库
  -E, --encoding=ENCODING     转储以ENCODING形式编码的数据
  -n, --schema=SCHEMA      只转储指定名称的模式
 -N, --exclude-schema=SCHEMA     不转储已命名的模式
  -o, --oids               在转储中包括 OID
  -O, --no-owner           在明文格式中, 忽略恢复对象所属者
  -s, --schema-only        只转储模式, 不包括数据
  -S, --superuser=NAME     在转储中, 指定的超级用户名
  -t, --table=TABLE        只转储指定名称的表
  -T, --exclude-table=TABLE       只转储指定名称的表
  -x, --no-privileges      不要转储权限 (grant/revoke)
  --binary-upgrade         只能由升级工具使用
  --column-inserts          以带有列名的INSERT命令形式转储数据
  --disable-dollar-quoting     取消美元 (符号) 引号, 使用 SQL 标准引号
  --disable-triggers         在只恢复数据的过程中禁用触发器
  --inserts                 以INSERT命令,而不是COPY命令的形式转储数据
  --no-security-labels        do not dump security label assignments
  --no-tablespaces           不转储表空间分配信息
  --no-unlogged-table-data    do not dump unlogged table data
  --quote-all-identifiers     quote all identifiers, even if not key words
  --serializable-deferrable   wait until the dump can run without anomalies
 --use-set-session-authorization
   使用 SESSION AUTHORIZATION 命令代替ALTER OWNER 命令来设置所有权

pg_restore [option…]

参数:
filename
    声明要恢复的备份文件的位置。如果没有声明,则使用标准输入。 
-a
--data-only
    只恢复数据,而不恢复表模式(数据定义)。 
-c
--clean
    创建数据库对象前先清理(删除)它们。 
-C
--create
    在恢复数据库之前先创建它。(如果出现了这个选项,和 -d 在一起的数据库名只是用于发出最初的CREATE DATABASE命令。 所有数据都恢复到名字出现在归档中的数据库中去。)
-d dbname
--dbname=dbname
    与数据库 dbname 联接并且直接恢复到该数据库中。 
-e
--exit-on-error
    如果在向数据库发送 SQL 命令的时候碰到错误,则退出。 缺省是继续执行并且在恢复结束时显示一个错误计数。 
    
-f filename
--file=filename
    声明生成的脚本的输出文件,或者出现-l 选项时用于列表的文件,缺省是标准输出。 
    
-F format
--format=format
    声明备份文件的格式。因为pg_restore 会自动判断格式,所以如果声明了,它可以是下面之一:
    t 备份是一个 tar 归档。 使用这个格式允许在恢复数据库的时候重新排序和/或把表模式元素排除出去。 同时还可能在恢复的时候限制装载的数据。 
    c 备份的格式是来自pg_dump的客户化格式。 这是最灵活的格式,因为它允许重新对数据排序,也允许重载表模式元素。 缺省时这个格式是压缩的。 

-i
--ignore-version
    忽略数据库版本检查。 
    
-I index
--index=index
    只恢复命名的索引。 
    
-l
--list
    列出备份的内容。这个操作的输出可以用 -L 选项限制和重排所恢复的项目。 
    
-L list-file
--use-list=list-file
    只恢复在 list-file 里面的元素,以它们在文件中出现的顺序。 你可以移动各个行并且也可以通过在行开头放 ';' 的方式注释。(见下文获取例子。) 
    
-n namespace
--schema=schema
    只恢复指定名字的模式里面的定义和/或数据。不要和 -s 选项混淆。 这个选项可以和 -t 选项一起使用。 
    
-O
--no-owner
    不要输出设置对象的权限,以便与最初的数据库匹配的命令。 缺省时,pg_restore 发出 ALTER OWNER 或 SET SESSION AUTHORIZATION 语句设置创建出来的模式元素的所有者权限。 
    如果最初的数据库连接不是由超级用户(或者是拥有所有创建出来的对象的同一个用户)发起的,那么这些语句将失败。 使用 -O,那么任何用户都可以用于初始的连接,并且这个用户将拥有所有创建出来的对象。 
    
-P function-name(argtype [, ...])
--function=function-name(argtype [, ...])
    只恢复指定的命名函数。请注意仔细拼写函数名及其参数,应该和转储的内容列表中的完全一样。 
    
-R
--no-reconnect
    这个选项已经废弃了,但是为了保持向下兼容仍然接受。 
    
-s
--schema-only
    只恢复表结构(数据定义)。不恢复数据,序列值将重置。 
    
-S username
--superuser=username
    设置关闭触发器时声明超级用户的用户名。 只有在设置了 --disable-triggers 的时候才有用。 
    
-t table
--table=table
    只恢复表指定的表的定义和/或数据。 
    
-T trigger
--trigger=trigger
    只恢复指定的触发器。 
    
-v
--verbose
    声明冗余模式。 
    
-x
--no-privileges
--no-acl
    避免 ACL 的恢复(grant/revoke 命令)。 
    
-X use-set-session-authorization
--use-set-session-authorization
    输出 SQL 标准的 SET SESSION AUTHORIZATION 命令,而不是 OWNER TO 命令。 这样令转储与标准兼容的更好,但是根据转储中对象的历史,这个转储可能不能恰当地恢复。 
    
-X disable-triggers
--disable-triggers
    这个选项只有在执行仅恢复数据的时候才相关。它告诉 pg_restore 在装载数据的时候执行一些命令临时关闭在目标表上的触发器。 如果你在表上有完整性检查或者其它触发器, 而你又不希望在装载数据的时候激活它们,那么可以使用这个选项。
    目前,为 --disable-triggers 发出的命令必须以超级用户发出。 因此,你应该也要用 -S 声明一个超级用户名,或者更好是设置 --use-set-session-authorization 并且以 PostgreSQL 超级用户身份运行 pg_restore。 

查看版本

  1. 查看客户端版本

    psql --version
    
  2. 查看服务器版本

    select version();
    SELECT current_setting('server_version_num');
    

以下内容转自:https://karloespiritu.github.io/cheatsheets/postgresql/

Basic Commands

Login to postgresql

psql -U postgrespsql -d mydb -U myuser -Wpsql -h myhost -d mydb -U myuser -Wpsql -U myuser -h myhost "dbname=mydb sslmode=require" # ssl connection

Default Admin Login

sudo -u postgres psql -U postgressudo -u postgres psql

List databases on postgresql server

psql -l [-U myuser] [-W]

Turn off line pager pagination in psql:

\pset pager

Determine system tables

select * from pg_tables where tableowner = 'postgres';

List databases from within a pg shell

\l

List databases from UNIX command prompt

psql -U postgres -l

Describe a table

\d tablename

Quit psql

\q

Switch postgres database within admin login shell

\connect databasename

Reset a user password as admin

alter user usertochange with password 'new_passwd';

Show all tables

\dt

List all Schemas

\dn

List all users

\du

Load data into postgresql

psql -W -U username -H hostname < file.sql

Dump (Backup) Data into file

pg_dump -W -U username -h hostname database_name > file.sql

Increment a sequence

SELECT nextval('my_id_seq');

Create new user

CREATE USER lemmy WITH PASSWORD 'myPassword';# or
sudo -u postgres createuser lemmy -W

Change user password

ALTER USER Postgres WITH PASSWORD 'mypass';

Grant user createdb privilege

ALTER USER myuser WITH createdb;

Create a superuser user

create user mysuper with password '1234' SUPERUSER# or even bettercreate user mysuper with password '1234' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION;# orsudo -u postgres createuser lemmy -W -s

Upgrade an existing user to superuser

alter user mysuper with superuser;# or even betteralter user mysuper with SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION

Show Database Version

SELECT version();

Change Database Owner

alter database database_name owner to new_owner;

Copy a database

CREATE DATABASE newdb WITH TEMPLATE originaldb;

View Database Connections

SELECT * FROM pg_stat_activity;

View show data directory (works on 9.1+)

show data_directory;

Show run-time parameters

show all;select * from pg_settings;

Show the block size setting

# show block_size; block_size------------ 8192(1 row)

Show stored procedure source

SELECT prosrc FROM pg_proc WHERE proname = 'procname'

Grant examples

# readonly to all tables for myusergrant select on all tables in schema public to myuser;# all privileges on table1 and table2 to myusergrant all privileges on table1, table2, table3 to myuser;

Restore Postgres .dump file

pg_restore --verbose --clean --no-acl --no-owner -h localhost -U myuser -d mydb latest.dump

source

Find all active sessions and kill them (i.e. for when needing to drop or rename db)

Source: http://stackoverflow.com/questions/5408156/how-to-drop-a-postgresql-database-if-there-are-active-connections-to-it

# Postgres 9.6 and aboveSELECT pg_terminate_backend(pg_stat_activity.pid)FROM pg_stat_activityWHERE pg_stat_activity.datname = 'TARGET_DB' AND pid <> pg_backend_pid();
# Postgres 9.6 and belowSELECT pg_terminate_backend(pg_stat_activity.procpid)FROM pg_stat_activityWHERE pg_stat_activity.datname = 'TARGET_DB'AND procpid <> pg_backend_pid();

Handy Queries

-- List procedure/functionSELECT * FROM pg_proc WHERE proname='__procedurename__';
-- List view (including the definition)SELECT * FROM pg_views WHERE viewname='__viewname__';
-- Show DB table space in useSELECT pg_size_pretty(pg_total_relation_size('__table_name__'));:
-- Show DB space in useSELECT pg_size_pretty(pg_database_size('__database_name__'));
-- Show current user's statement timeoutshow statement_timeout;
-- Show table indexesSELECT * FROM pg_indexes WHERE tablename='__table_name__' AND schemaname='__schema_name__';
-- Get all indexes from all tables of a schema:SELECT   t.relname AS table_name,   i.relname AS index_name,   a.attname AS column_nameFROM   pg_class t,   pg_class i,   pg_index ix,   pg_attribute a,   pg_namespace nWHERE   t.oid = ix.indrelid   AND i.oid = ix.indexrelid   AND a.attrelid = t.oid   AND a.attnum = ANY(ix.indkey)   AND t.relnamespace = n.oid   AND n.nspname = 'kartones'ORDER BY   t.relname,   i.relname
-- Queries being executed at a certain DBSELECT datname, application_name, pid, backend_start, query_start, state_change, state, query  FROM pg_stat_activity  WHERE datname='__database_name__';
-- Get all queries from all dbs waiting for data (might be hung)SELECT * FROM pg_stat_activity WHERE waiting='t';

Query analysis

-- See the query plan for the given queryEXPLAIN __query__
-- See and execute the query plan for the given queryEXPLAIN ANALYZE __query__
-- Collect statisticsANALYZE [__table__]

Querying Data

From a Single Table

-- Query data in columns c1, c2 from a tableSELECT c1, c2 FROM t;
-- Query distinct rows from a tableSELECT DISTINCT c1FROM tWHERE condition;
-- Sort the result set in ascending or descending orderSELECT c1, c2FROM tORDER BY c1 ASC [DESC];
-- Skip offset of rows and return the next n rowsSELECT c1, c2FROM tORDER BY c1LIMIT nOFFSET offset;
-- Group rows using an aggregate functionSELECT c1, aggregate(c2)FROM tGROUP BY c1;
-- Filter groups using HAVING clauseSELECT c1, aggregate(c2) FROM tGROUP BY c1HAVING condition;

From Multiple Tables

-- Inner join t1 and t2SELECT c1, c2FROM t1INNER JOIN t2ON condition;
-- Left join t1 and t1SELECT c1, c2FROM t1LEFT JOIN t2ON condition;
-- Right join t1 and t2SELECT c1, c2FROM t1RIGHT JOIN t2ON condition;
-- Perform full outer joinSELECT c1, c2FROM t1FULL OUTER JOIN t2ON condition;
-- Produce a Cartesian product of rows in tablesSELECT c1, c2FROM t1CROSS JOIN t2;
-- Another way to perform cross joinSELECT c1, c2FROM t1, t2;
-- Join t1 to itself using INNER JOIN clauseSELECT c1, c2FROM t1 AINNER JOIN t2 B ON condition

Using SQL Operators

-- Combine rows from two queriesSELECT c1, c2 FROM t1UNION [ALL]SELECT c1, c2 FROM t2;
-- Return the intersection of two queriesSELECT c1, c2 FROM t1INTERSECTSELECT c1, c2 FROM t2;
-- Subtract a result set from another result setSELECT c1, c2 FROM t1EXCEPTSELECT c1, c2 FROM t2;
-- Query rows using pattern matching %, _SELECT c1, c2 FROM t1WHERE c1 [NOT] LIKE pattern;
-- Query rows in a listSELECT c1, c2FROM tWHERE c1[NOT] IN value_list;
-- Query rows between two valuesSELECT c1, c2FROM tWHERE c1BETWEEN low AND high;
-- Check if values in a table is NULL or notSELECT c1, c2 FROM tWHERE c1 IS [NOT] NULL;

参考资料