Supervisord 使用记录

Supervisord 是用 Python 实现,由孙大力同学介绍使用。早知有这东西,我也不用自己写 daemon.php 来守护队列程序了 =_=!

特点

  1. 程序的多进程启动,可以配置同时启动的进程数,而不需要一个个启动
  2. 程序的退出码,可以根据程序的退出码来判断是否需要自动重启
  3. 程序所产生日志的处理
  4. 进程初始化的环境,包括目录,用户,umask,关闭进程所需要的信号等等
  5. 手动管理进程(开始启动、查看进程状态)Web 界面,和 xmlrpc 接口

1、安装 setuptools

yum install python-setuptools
或者
wget --no-check-certificate https://bootstrap.pypa.io/ez_setup.py -O - | sudo python

2、安装 supervisor

easy_install pip
pip install supervisor
或者
easy_install supervisor

3、初始化配置文件

echo_supervisord_conf > /etc/supervisord.conf

4、引入配置文件

# 建立配置文件子目录
mkdir /etc/supervisord.d/

# 去除注释并修改路径
[include]
files = /etc/supervisord.d/*.ini

添加一个程序

[program:Queue_Sms]
process_name=%(program_name)s_%(process_num)02d
redirect_stderr=true
autostart=true
autorestart=true
numprocs=8
command=/usr/bin/php /home/wwwroot/ai_gmall_server/artisan queue:work --tries=3 --sleep=3
stdout_logfile=/home/wwwlogs/supervisord_ai_gmall_server.out

启动服务(重要)

supervisord -c /etc/supervisord.conf

常用命令

控制命令基本都通过 supervisorctl 执行,输入 help 可以看到命令列表。这是一些常用命令:

# 注意:本命令只会重启当前监听的进程,并不会重新加载 supervisord.conf
supervisorctl restart XXX

# 重新加载 supervisord.conf,纯加载配置,不会对进程有任何影响
supervisorctl reread

# 重新加载 supervisord.conf,重启有变更的进程,原有未变更的进程不受影响
# 注意:测试下来 update 已经包含了 reread 命令执行后的效果,可以说 reread 命令然并卵
supervisorctl update

# 强制重新启动配置中的所有程序(不论 supervisord.conf 是否修改过)
supervisorctl reload

# 列出当前监听的进程和进程PID
supervisorctl status

# 不解释了
supervisorctl stop/start XXX

日志文件位置

/tmp/supervisord.log

Web控制台

程序支持使用HTTP方式控制运行状态。在 supervisord.conf 中开启 httpserver:

[inet_http_server] port=127.0.0.1:9001 username=user password=123

打开浏览器,输入 127.0.0.1:9001,用户名user,密码123,就能看到控制台了:

每次修改完 supervisor.conf 后,都需要重新加载配置文件:supervisorctl reload

配置文件说明

官方配置说明:http://supervisord.org/configuration.html

[unix_http_server]

file=/tmp/supervisor.sock         ; socket文件的路径,supervisorctl用XML_RPC和supervisord通
                                  ; 信就是通过它进行的。如果不设置的话,supervisorctl也就不能用了
                                  ; 不设置的话,默认为none。 非必须设置
;chmod=0700                       ; 这个简单,就是修改上面的那个socket文件的权限为0700
                                  ; 不设置的话,默认为0700。 非必须设置
;chown=nobody:nogroup             ; 这个一样,修改上面的那个socket文件的属组为user.group
                                  ; 不设置的话,默认为启动supervisord进程的用户及属组。非必须设置
;username=user                    ; 使用supervisorctl连接的时候,认证的用户
                                  ; 不设置的话,默认为不需要用户。 非必须设置
;password=123                     ; 和上面的用户名对应的密码,可以直接使用明码,也可以使用SHA加密
                                  ; 如:{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d
                                  ; 默认不设置,非必须设置

;[inet_http_server]               ; 侦听在TCP上的socket,Web Server和远程的supervisorctl都要用到他
                                  ; 不设置的话,默认为不开启。非必须设置
;port=127.0.0.1:9001              ; 这个是侦听的IP和端口,侦听所有IP用 :9001或*:9001。
                                  ; 这个必须设置,只要上面的[inet_http_server]开启了,就必须设置它
;username=user                    ; 这个和上面的uinx_http_server一个样。非必须设置
;password=123                     ; 这个也一个样。非必须设置

[supervisord]                     ; 这个主要是定义supervisord这个服务端进程的一些参数的
                                  ; 这个必须设置,不设置,supervisor就不用干活了
logfile=/tmp/supervisord.log      ; 这个是supervisord这个主进程的日志路径,注意和子进程的日志不搭嘎。
                                  ; 默认路径$CWD/supervisord.log,$CWD是当前目录,非必须设置
logfile_maxbytes=50MB             ; 这个是上面那个日志文件的最大的大小,当超过50M的时候,会生成一个新的日
                                  ; 志文件。当设置为0时,表示不限制文件大小
                                  ; 默认值是50M,非必须设置。
logfile_backups=10                ; 日志文件保持的数量,上面的日志文件大于50M时,就会生成一个新文件。文件
                                  ; 数量大于10时,最初的老文件被新文件覆盖,文件数量将保持为10
                                  ; 当设置为0时,表示不限制文件的数量。
                                  ; 默认情况下为10,非必须设置
loglevel=info                     ; 日志级别,有critical, error, warn, info, debug, trace, or blather等
                                  ; 默认为info,非必须设置项
pidfile=/tmp/supervisord.pid      ; supervisord的pid文件路径。
                                  ; 默认为$CWD/supervisord.pid,非必须设置
nodaemon=false                    ; 如果是true,supervisord进程将在前台运行
                                  ; 默认为false,也就是后台以守护进程运行,非必须设置
minfds=1024                       ; 这个是最少系统空闲的文件描述符,低于这个值supervisor将不会启动。
                                  ; 系统的文件描述符在这里设置cat /proc/sys/fs/file-max
                                  ; 默认情况下为1024,非必须设置
minprocs=200                      ; 最小可用的进程描述符,低于这个值supervisor也将不会正常启动。
                                  ; ulimit  -u这个命令,可以查看linux下面用户的最大进程数
                                  ; 默认为200,非必须设置
;umask=022                        ; 进程创建文件的掩码
                                  ; 默认为022,非必须设置项
;user=chrism                      ; 这个参数可以设置一个非root用户,当我们以root用户启动supervisord之后。
                                  ; 我这里面设置的这个用户,也可以对supervisord进行管理
                                  ; 默认情况是不设置,非必须设置项
;identifier=supervisor            ; 这个参数是supervisord的标识符,主要是给XML_RPC用的。当你有多个
                                  ; supervisor的时候,而且想调用XML_RPC统一管理,就需要为每个
                                  ; supervisor设置不同的标识符了
                                  ; 默认是supervisord,非必需设置
;directory=/tmp                   ; 这个参数是当supervisord作为守护进程运行的时候,设置这个参数的话,启动
                                  ; supervisord进程之前,会先切换到这个目录
                                  ; 默认不设置,非必须设置
;nocleanup=true                   ; 这个参数当为false的时候,会在supervisord进程启动的时候,把以前子进程
                                  ; 产生的日志文件(路径为AUTO的情况下)清除掉。有时候咱们想要看历史日志,当
                                  ; 然不想日志被清除了。所以可以设置为true
                                  ; 默认是false,有调试需求的同学可以设置为true,非必须设置
;childlogdir=/tmp                 ; 当子进程日志路径为AUTO的时候,子进程日志文件的存放路径。
                                  ; 默认路径是这个东西,执行下面的这个命令看看就OK了,处理的东西就默认路径
                                  ; python -c "import tempfile;print tempfile.gettempdir()"
                                  ; 非必须设置
;environment=KEY="value"          ; 这个是用来设置环境变量的,supervisord在linux中启动默认继承了linux的
                                  ; 环境变量,在这里可以设置supervisord进程特有的其他环境变量。
                                  ; supervisord启动子进程时,子进程会拷贝父进程的内存空间内容。 所以设置的
                                  ; 这些环境变量也会被子进程继承。
                                  ; 小例子:environment=name="haha",age="hehe"
                                  ; 默认为不设置,非必须设置
;strip_ansi=false                 ; 这个选项如果设置为true,会清除子进程日志中的所有ANSI 序列。什么是ANSI
                                  ; 序列呢?就是我们的\n,\t这些东西。
                                  ; 默认为false,非必须设置

; 这个选项是给XML_RPC用的,当然你如果想使用supervisord或者web server 这
; 个选项必须要开启的

[rpcinterface:supervisor]

supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; 这个主要是针对supervisorctl的一些配置
[supervisorctl]

serverurl=unix:///tmp/supervisor.sock ; 这个是supervisorctl本地连接supervisord的时候,本地UNIX socket
                                      ; 路径,注意这个是和前面的[unix_http_server]对应的
                                      ; 默认值就是unix:///tmp/supervisor.sock,非必须设置
;serverurl=http://127.0.0.1:9001      ; 这个是supervisorctl远程连接supervisord的时候,用到的TCP socket路径
                                      ; 注意这个和前面的[inet_http_server]对应
                                      ; 默认就是http://127.0.0.1:9001,非必须项

;username=chris                       ; 用户名
                                      ; 默认空,非必须设置
;password=123                         ; 密码
                                      ; 默认空,非必须设置
;prompt=mysupervisor                  ; 输入用户名密码时候的提示符
                                      ; 默认supervisor,非必须设置
;history_file=~/.sc_history           ; 这个参数和shell中的history类似,我们可以用上下键来查找前面执行过的命令
                                      ; 默认是no file的,所以我们想要有这种功能,必须指定一个文件,非
                                      ; 必须设置

; 这个就是咱们要管理的子进程了,":"后面的是名字,最好别乱写和实际进程
; 有点关联最好。这样的program我们可以设置一个或多个,一个program就是
; 要被管理的一个进程

;[program:theprogramname]
;command=/bin/cat              ; 这个就是我们的要启动进程的命令路径了,可以带参数
                               ; 例子:/home/test.py -a 'hehe'
                               ; 有一点需要注意的是,我们的command只能是那种在终端运行的进程,不能是
                               ; 守护进程。这个想想也知道了,比如说command=service httpd start。
                               ; httpd这个进程被linux的service管理了,我们的supervisor再去启动这个命令
                               ; 这已经不是严格意义的子进程了。
                               ; 这个是个必须设置的项
;process_name=%(program_name)s ; 这个是进程名,如果我们下面的numprocs参数为1的话,就不用管这个参数
                               ; 了,它默认值%(program_name)s也就是上面的那个program冒号后面的名字,
                               ; 但是如果numprocs为多个的话,那就不能这么干了。想想也知道,不可能每个
                               ; 进程都用同一个进程名吧。


;numprocs=1                    ; 启动进程的数目。当不为1时,就是进程池的概念,注意process_name的设置
                               ; 默认为1    ,非必须设置
;directory=/tmp                ; 进程运行前,会前切换到这个目录
                               ; 默认不设置,非必须设置
;umask=022                     ; 进程掩码,默认none,非必须
;priority=999                  ; 子进程启动关闭优先级,优先级低的,最先启动,关闭的时候最后关闭
                               ; 默认值为999 ,非必须设置
;autostart=true                ; 如果是true的话,子进程将在supervisord启动后被自动启动
                               ; 默认就是true   ,非必须设置
;autorestart=unexpected        ; 这个是设置子进程挂掉后自动重启的情况,有三个选项,false,unexpected
                               ; 和true。如果为false的时候,无论什么情况下,都不会被重新启动,
                               ; 如果为unexpected,只有当进程的退出码不在下面的exitcodes里面定义的退
                               ; 出码的时候,才会被自动重启。当为true的时候,只要子进程挂掉,将会被无
                               ; 条件的重启
;startsecs=1                   ; 这个选项是子进程启动多少秒之后,此时状态如果是running,则我们认为启
                               ; 动成功了
                               ; 默认值为1 ,非必须设置
;startretries=3                ; 当进程启动失败后,最大尝试启动的次数,当超过3次后,supervisor将把
                               ; 此进程的状态置为FAIL
                               ; 默认值为3 ,非必须设置
;exitcodes=0,2                 ; 注意和上面的的autorestart=unexpected对应,exitcodes里面的定义的
                               ; 退出码是expected的。
;stopsignal=QUIT               ; 进程停止信号,可以为TERM, HUP, INT, QUIT, KILL, USR1, or USR2等信号
                               ; 默认为TERM ,当用设定的信号去干掉进程,退出码会被认为是expected
                               ; 非必须设置
;stopwaitsecs=10               ; 这个是当我们向子进程发送stopsignal信号后,到系统返回信息
                               ; 给supervisord,所等待的最大时间。 超过这个时间,supervisord会向该
                               ; 子进程发送一个强制kill的信号。
                               ; 默认为10秒,非必须设置
;stopasgroup=false             ; 这个东西主要用于,supervisord管理的子进程,这个子进程本身还有
                               ; 子进程。那么我们如果仅仅干掉supervisord的子进程的话,子进程的子进程
                               ; 有可能会变成孤儿进程。所以咱们可以设置可个选项,把整个该子进程的
                               ; 整个进程组都干掉。 设置为true的话,一般killasgroup也会被设置为true。
                               ; 需要注意的是,该选项发送的是stop信号
                               ; 默认为false,非必须设置,
;killasgroup=false             ; 这个和上面的stopasgroup类似,不过发送的是kill信号
;user=chrism                   ; 如果supervisord是root启动,我们在这里设置这个非root用户,可以用来
                               ; 管理该program
                               ; 默认不设置,非必须设置项
;redirect_stderr=true          ; 如果为true,则stderr的日志会被写入stdout日志文件中
                               ; 默认为false,非必须设置
;stdout_logfile=/a/path        ; 子进程的stdout的日志路径,可以指定路径,AUTO,none等三个选项。
                               ; 设置为none的话,将没有日志产生。设置为AUTO的话,将随机找一个地方
                               ; 生成日志文件,而且当supervisord重新启动的时候,以前的日志文件会被
                               ; 清空。当 redirect_stderr=true的时候,sterr也会写进这个日志文件
;stdout_logfile_maxbytes=1MB   ; 日志文件最大大小,和[supervisord]中定义的一样。默认为50
;stdout_logfile_backups=10     ; 和[supervisord]定义的一样。默认10
;stdout_capture_maxbytes=1MB   ; 这个东西是设定capture管道的大小,当值不为0的时候,子进程可以从stdout
                               ; 发送信息,而supervisor可以根据信息,发送相应的event。
                               ; 默认为0,为0的时候表达关闭管道,非必须项
;stdout_events_enabled=false   ; 当设置为ture的时候,当子进程由stdout向文件描述符中写日志的时候,将
                               ; 触发supervisord发送PROCESS_LOG_STDOUT类型的event
                               ; 默认为false,非必须设置
;stderr_logfile=/a/path        ; 这个东西是设置stderr写的日志路径,当redirect_stderr=true。这个就不用
                               ; 设置了,设置了也是白搭。因为它会被写入stdout_logfile的同一个文件中
                               ; 默认为AUTO,也就是随便找个地存,supervisord重启被清空,非必须设置
;stderr_logfile_maxbytes=1MB   ; 这个出现好几次了,就不重复了
;stderr_logfile_backups=10     ; 这个也是
;stderr_capture_maxbytes=1MB   ; 这个一样,和stdout_capture一样。 默认为0,关闭状态
;stderr_events_enabled=false   ; 这个也是一样,默认为false
;environment=A="1",B="2"       ; 这个是该子进程的环境变量,和别的子进程是不共享的
;serverurl=AUTO                ;

; 这个东西其实和program的地位是一样的,也是suopervisor启动的子进程
; 不过它干的活是订阅supervisord发送的event。他的名字就叫listener了
; 我们可以在listener里面做一系列处理,比如报警等等

;[eventlistener:theeventlistenername]

;command=/bin/eventlistener    ; 这个和上面的program一样,表示listener的可执行文件的路径
;process_name=%(program_name)s ; 这个也一样,进程名,当下面的numprocs为多个的时候,才需要。
;numprocs=1                    ; 相同的listener启动的个数
;events=EVENT                  ; event事件的类型,也就是说,只有写在这个地方的事件类型。才会被发送

;buffer_size=10                ; 这个是event队列缓存大小,单位不太清楚,楼主猜测应该是个吧。当buffer
                               ; 超过10的时候,最旧的event将会被清除,并把新的event放进去。
   ;                             默认值为10,非必须选项
;directory=/tmp                ; 进程执行前,会切换到这个目录下执行
                               ; 默认为不切换,非必须
;umask=022                     ; 淹没,默认为none,不说了
;priority=-1                   ; 启动优先级,默认-1,也不扯了
;autostart=true                ; 是否随supervisord启动一起启动,默认true
;autorestart=unexpected        ; 是否自动重启,和program一个样,分true,false,unexpected等,注意
                               ; unexpected和exitcodes的关系
;startsecs=1                   ; 也是一样,进程启动后跑了几秒钟,才被认定为成功启动,默认1
;startretries=3                ; 失败最大尝试次数,默认3
;exitcodes=0,2                 ; 期望或者说预料中的进程退出码,
;stopsignal=QUIT               ; 干掉进程的信号,默认为TERM,比如设置为QUIT,那么如果QUIT来干这个进程
                               ; 那么会被认为是正常维护,退出码也被认为是expected中的
;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false             ; send stop signal to the UNIX process group (default false)
;killasgroup=false             ; SIGKILL the UNIX process group (def false)
;user=chrism                   ; 设置普通用户,可以用来管理该listener进程。
                               ; 默认为空,非必须设置
;redirect_stderr=true          ; 为true的话,stderr的log会并入stdout的log里面
                               ; 默认为false,非必须设置
;stdout_logfile=/a/path        ; 这个不说了,好几遍了
;stdout_logfile_maxbytes=1MB   ; 这个也是
;stdout_logfile_backups=10     ; 这个也是
;stdout_events_enabled=false   ; 这个其实是错的,listener是不能发送event
;stderr_logfile=/a/path        ; 这个也是
;stderr_logfile_maxbytes=1MB   ; 这个也是
;stderr_logfile_backups        ; 这个不说了
;stderr_events_enabled=false   ; 这个也是错的,listener不能发送event
;environment=A="1",B="2"       ; 这个是该子进程的环境变量
                               ; 默认为空,非必须设置
;serverurl=AUTO                ; override serverurl computation (childutils)

; 这个东西就是给programs分组,划分到组里面的program。我们就不用一个一个去操作了
; 我们可以对组名进行统一的操作。 注意:program被划分到组里面之后,就相当于原来
; 的配置从supervisor的配置文件里消失了,supervisor只会对组进行管理,而不再
; 会对组里面的单个program进行管理了

;[group:thegroupname]  ;
;programs=progname1,progname2  ; 组成员,用逗号分开,这个是个必须的设置项
;priority=999                  ; 优先级,相对于组和组之间说的,默认999,非必须选项

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

; 这个东西挺有用的,当我们要管理的进程很多的时候,写在一个文件里面
; 就有点大了。我们可以把配置信息写到多个文件中,然后include过来

[include]
files = /etc/supervisord.d/*.ini

一些坑

supervisor 不能监控 daemon (fork) 形式的背影进程,否则 supervisor> status 会提示: BACKOFF Exited too quickly (process log may have details)

例如要监控 redis 进程,则必须修改 redis.conf 的 daemonize=no 猜想 supervisor 启动 command 是应该监控的是启动 command 对应的进程编号,一般守护进程的做法就是 fork 一个子进程,父进程马上退出,以致于 supervisor 无法得到子进程的的进程ID

参考文章:

Git AutoCRLF 与 SafeCRLF 换行符问题

Windows和Linux的换行符不一样:

  • CR回车 LF换行
  • Windows/Dos CRLF \r\n
  • Linux/Unix LF \n
  • MacOS CR \r

解决方法:打开命令行,进行设置,如果你是在Windows下开发,建议设置autocrlf为true。 如果文件编码是UTF8且含中文,那设置autocrlf=false,把所有文件转换为Linux编码(即LF\n),开启SafeCRLF检查。

一、AutoCRLF

提交时转换为LF,检出时转换为CRLF

git config –global core.autocrlf true

提交时转换为LF,检出时不转换

git config –global core.autocrlf input

提交检出均不转换

git config –global core.autocrlf false

二、SafeCRLF

拒绝提交包含混合换行符的文件

git config –global core.safecrlf true

允许提交包含混合换行符的文件

git config –global core.safecrlf false

提交包含混合换行符的文件时给出警告

git config –global core.safecrlf warn

有意思的人脸识别API

1、微软提供的接口

https://www.projectoxford.ai/face

应用:http://how-old.net/

2、40多个关于人脸检测/识别的API、库和软件

http://blog.jobbole.com/45936/

3、百度开放云 人脸识别 BFR

https://bce.baidu.com/product/bfr.html

HTTP2.0的奇妙日常(转)

HTTP2.0性能增强的核心:二进制分帧

HTTP 2.0最大的特点: 不会改动HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念上一如往常,却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量。而之所以叫2.0,是在于新增的二进制分帧层。 既然又要保证HTTP的各种动词,方法,首部都不受影响,那就需要在应用层(HTTP2.0)和传输层(TCP or UDP)之间增加一个二进制分帧层。 在二进制分帧层上,HTTP 2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码 ,其中HTTP1.x的首部信息会被封装到Headers帧,而我们的request body则封装到Data帧里面。

然后,HTTP 2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。

HTTP2.0 首部压缩

HTTP 2.0 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只 需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么 首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。

如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP 2.0 的连接存续期内始终存在,由客户端和服务器共同渐进地更新 。

“好了,现在你倒是给我解释一下,这里使用自动化合并文件和Sprite合图是什么回事?”晨伯不解

“本质上,当然是为了减少请求啦,通过多个js或css合并成一个文件,多张小图片拼合成Sprite图,可以让多个HTTP请求减少为一个,减少额外的协议开销,而提升性能。”如是道也

“当然,一个HTTP的请求的body太大也是不合理的,有个度。文件的合并也会牺牲模块化和缓存粒度,可以把“稳定”的代码or 小图 合并为一个文件or一张Sprite,让其充分地缓存起来,从而区分开迭代快的文件” 我不明白晨伯的问题,就稍微补充了一下方案。

所有的HTTP2.0的请求都在一个TCP链接上 HTTP2.0所有通信都是在一个TCP连接上完成。HTTP 2.0 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应 着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。就好比,我请求一个页面http://www.qq.com。页面上所有的资源请求都是客户端与服务器上的一条TCP上请求和响应的!

有关注TCP性能的同学就会知道,HTTP性能的关键在于低延迟而不是高带宽!大多数HTTP 连接的时间都很短,而且是突发性的,但TCP 只在长时间连接传输大块数据时效率才最高。HTTP 2.0 通过让所有数据流共用同一个连接,可以更有效地使用TCP 连接,让高带宽也能真正的服务于HTTP的性能提升。

同时,单链接多资源的方式,使到至上而下的层面都得到了好处:

  • 可以减少服务链接压力,内存占用少了,连接吞吐量大了
  • 由于 TCP 连接减少而使网络拥塞状况得以改观;
  • 慢启动时间减少,拥塞和丢包恢复速度更快。

也就是说,“资源合并减少请求”的优化手段对于HTTP2.0来说是没有效果的,只会增大无用的工作量而已。

他说得好有道理,我竟然掩脸而对(因为脸被打疼了)。

“你在再我说说,这些cdn1.cn,cdn2.cn,cdn3.cn是什么回事啊”晨伯又问到。

“因为HTTP1.x上如果一个只用一个持久链接,请求只能一个一个顺序请求,为了高效地并行下载资源,浏览器允许我们打开多个TCP会话,但是一个域名下限制6个链接。为了突破这些限制,我们可以域名分区,提高并行下载资源能力…..”我只好把我当年知道的说出来

当时我就有预感要giveyoufive,而晨伯总是按套路出牌….

并行双向字节流的请求和响应 在HTTP2.0上,客户端和服务器可以把HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。注意,同一链接上有多个不同方向的数据流在传输。客户端可以一边乱序发送stream,也可以一边接收者服务器的响应,而服务器那端同理。

把 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP 2.0 最 重要的一项增强。事实上,这个机制会在整个 Web 技术栈中引发一系列连锁反应, 从而带来巨大的性能提升,因为:

  • 可以并行交错地发送请求,请求之间互不影响
  • 可以并行交错地发送响应,响应之间互不干扰
  • 只使用一个连接即可并行发送多个请求和响应
  • 消除不必要的延迟,从而减少页面加载的时间

那么也就是说“域名分区”这种优化手段对于HTTP2.0是无用的,因为资源都是并行交错发送,且没有限制,不需要额外的多域名并行下载。

“既然所有资源都是并行交错发送,会不会出现这样的情况【浏览器明明在等关键的 CSS 和JS,你TMD的服务器还在发送图片】” 我疑问道。

HTTP2.0的请求优先级

每个HTTP2.0流里面有个优先值,这个优先值确定着客户端和服务器处理不同的流采取不同的优先级策略,高优先级的流都应该优先发送,但又不会绝对的。绝对地准守,可能又会引入首队阻塞的问题:高优先级的请求慢导致阻塞其他资源交付。分配处理资源和客户端与服务器间的带宽,不同优先级的混合也是必须的。

“有了优先级,HTTP2.0根本不会发生【浏览器明明在等关键的 CSS 和JS,你TMD的服务器还在发送黄图】”晨伯道。

“我根本没有说是服务器在发黄图,好不好。”我吐槽了一下。

“还有还有,你这里的一段base64内嵌图片又是什么回事?是黄图吗?”晨伯又挑战我了。

内嵌图片这种,有使用条件的优化手段,我还是不要说话好,不然的话按照这个故事的尿性,他应该又要飞拳我。

HTTP2.0的服务器推送

HTTP 2.0 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,服务器可以强奸你的浏览器,哦不,应该是,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。

当浏览器请求一个html,服务器其实大概知道你是接下来要请求资源了,而不需要等待浏览器得到html后解析页面再发送资源请求。我们常用的内嵌图片也可以理解为一种强制的服务器推送:我请求html,却内嵌了张黄图。

有了HTTP2.0的服务器推送,HTTP1.x时代的内嵌资源的优化手段也变得没有意义了。而且使用服务器推送的资源的方式更加高效,因为客户端还可以缓存起来,甚至可以由不同的页面共享(依旧遵循同源策略)。当然,你是个正直的浏览器,是可以决绝服务器推送的黄图的。

参考文章:

Fiddler 抓取手机 https

开启选项

打开 Fiddler,点击工具栏的 Tools – Fiddler Options

  • 切换到 HTTPS 选项卡,勾选 Capture HTTPS CONNECTs + Decrypt HTTPS trafic
  • 切换到 Connections 选项卡,勾选 Allow remote computers to connect

在手机上安装 Fiddler 根证书

打开 iOS/Android 的浏览器, 访问 http://192.168.1.104:8888 点击底部的 FiddlerRoot certificate 链接将证书安装到手机上(DO_NOT_TRUST_…)

目的:让客户端在之后的通信过程中可以信任该根证书颁发的证书(介绍信)

Fiddler 抓包原理

Fiddler2 使用 man-in-the-middle (中间人) 攻击的方式来截取 HTTPS 流量。在 Web 浏览器面前 Fiddler2 假装成一个 HTTPS 服务器,而在真正的 HTTPS 服务器面前 Fiddler2 假装成浏览器。Fiddler2 会动态地生成 HTTPS 证书来伪装服务器。

我们看到 Fiddler 抓取 HTTPS 协议主要由以下几步进行:

  1. Fiddler 截获客户端发送给服务器的HTTPS请求,Fiddler 伪装成客户端向服务器发送请求进行握手 。
  2. 服务器发回相应,Fiddler 获取到服务器的 CA证书, 用根证书公钥进行解密,验证服务器数据签名,获取到服务器 CA 证书公钥。然后 Fiddler 伪造自己的CA证书, 冒充服务器证书传递给客户端浏览器。
  3. 与普通过程中客户端的操作相同,客户端根据返回的数据进行证书校验、生成密码 Pre_master,用 Fiddler 伪造的证书公钥加密,并生成 HTTPS 通信用的对称密钥 enc_key。
  4. 客户端将重要信息传递给服务器,又被 Fiddler 截获。Fiddler 将截获的密文用自己伪造证书的私钥解开, 获得并计算得到HTTPS通信用的对称密钥 enc_key。Fiddler 将对称密钥用服务器证书公钥加密传递给服务器。
  5. 与普通过程中服务器端的操作相同,服务器用私钥解开后建立信任,然后再发送加密的握手消息给客户端。
  6. Fiddler 截获服务器发送的密文,用对称密钥解开,再用自己伪造证书的私钥加密传给客户端。
  7. 客户端拿到加密信息后,用公钥解开,验证 HASH。握手过程正式完成,客户端与服务器端就这样建立了”信任“。

在之后的正常加密通信过程中,Fiddler如何在服务器与客户端之间充当第三者呢?

服务器—>客户端:Fiddler接收到服务器发送的密文, 用对称密钥解开, 获得服务器发送的明文。再次加密, 发送给客户端。客户端—>服务端:客户端用对称密钥加密,被Fiddler截获后,解密获得明文。再次加密,发送给服务器端。由于Fiddler一直拥有通信用对称密钥enc_key, 所以在整个HTTPS通信过程中信息对其透明。

从上面可以看到,Fiddler抓取HTTPS协议成功的关键是根证书(具体是什么,可Google),这是一个信任链的起点,这也是Fiddler伪造的CA证书能够获得客户端和服务器端信任的关键。接下来我们就来看如果设置让Fiddler抓取HTTPS协议。

参考文章:

Redis 阻塞、安全队列 BLPOP/BRPOPLPUSH

队列优先级怎么做?

方法1. 分N个redis list key,一个高级,一个普通级别 方法2. 如果队列是 rpush,高级的则插队用 lpush(缺点是高级的顺序是反的) 方法3. 用 blpop 的轮训弹出,例如:brpop([‘high_task_queue’, ‘low_task_queue’], 0) 方法4. 有序集合(非阻塞) 方法5. 二分查找在 list 中间找个位置插入

参考链接:http://www.2cto.com/kf/201408/325868.html

阻塞的优点?

节约性能,cli可以休息,不用一直连着,可以处于 WAIT 状态 非阻塞的意思是如果当前队列为空,要求调用方不断轮循(polling+sleep),这对使用者来说是非常不方便的。

==================================================

BLPOP BLPOP key [key …] timeout BLPOP是列表的阻塞式(blocking)弹出原语。

它是LPOP命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP命令阻塞,直到等待超时或发现可弹出元素为止。

当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。

非阻塞行为

当BLPOP被调用时,如果给定key内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。

当存在多个给定key时,BLPOP按给定key参数排列的先后顺序,依次检查各个列表。

假设现在有job、 command和request三个列表,其中job不存在,command和request都持有非空列表。考虑以下命令:

BLPOP job command request 0

BLPOP保证返回的元素来自command,因为它是按”查找job -> 查找command -> 查找request“这样的顺序,第一个找到的非空列表。

redis> DEL job command request  # 确保key都被删除
(integer) 0
redis> LPUSH command "update system..."  # 为command列表增加一个值
(integer) 1
redis> LPUSH request "visit page"  # 为request列表增加一个值
(integer) 1

redis> BLPOP job command request 0  # job列表为空,被跳过,紧接着command列表的第一个元素被弹出。
1) "command"    # 弹出元素所属的列表
2) "update system..."   # 弹出元素所属的值

阻塞行为

如果所有给定key都不存在或包含空列表,那么BLPOP命令将阻塞连接,直到等待超时,或有另一个客户端对给定key的任意一个执行LPUSH或RPUSH命令为止。

超时参数timeout接受一个以秒为单位的数字作为值。超时参数设为0表示阻塞时间可以无限期延长(block indefinitely) 。

redis> EXISTS job  # 确保两个key都不存在
(integer) 0
redis> EXISTS command
(integer) 0

redis> BLPOP job command 300  #因为key一开始不存在,所以操作会被阻塞,直到另一客户端对job或者command列表进行PUSH操作。
1) "job"  # 这里被push的是job
2) "do my home work"  # 被弹出的值
(26.26s)  # 等待的秒数

redis> BLPOP job command 5  # 等待超时的情况
(nil)
(5.66s) # 等待的秒数

相同的key被多个客户端同时阻塞

相同的key可以被多个客户端同时阻塞。 不同的客户端被放进一个队列中,按”先阻塞先服务”(first-BLPOP,first-served)的顺序为key执行BLPOP命令。 在MULTI/EXEC事务中的BLPOP

BLPOP可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在MULTI/EXEC块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行LPUSH或RPUSH命令。

因此,一个被包裹在MULTI/EXEC块内的BLPOP命令,行为表现得就像LPOP一样,对空列表返回nil,对非空列表弹出列表元素,不进行任何阻塞操作。

情况1:对非空列表进行操作

redis> RPUSH job programming
(integer) 1

redis> MULTI
OK

redis> BLPOP job 30
QUEUED

redis> EXEC  # 不阻塞,立即返回
1) 1) "job"
   2) "programming"

情况2:对空列表进行操作

redis> LLEN job  # 空列表
(integer) 0

redis> MULTI
OK

redis> BLPOP job 30
QUEUED

redis> EXEC  # 不阻塞,立即返回
1) (nil)

时间复杂度:O(1) 返回值:如果列表为空,返回一个nil。 反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的key,第二个元素是被弹出元素的值。

用 RPOPLPUSH 实现安全的队列

Redis的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。一个客户端通过 LPUSH 命令将消息放入队列中,而另一个客户端通过 RPOP 或者 BRPOP 命令取出队列中等待时间最长的消息。

不幸的是,上面的队列方法是『不安全』的,因为在这个过程中,一个客户端可能在取出一个消息之后崩溃,而未处理完的消息也就因此丢失。

使用 RPOPLPUSH 命令(或者它的阻塞版本 BRPOPLPUSH )可以解决这个问题:因为它不仅返回一个消息,同时还将这个消息添加到另一个备份列表当中,如果一切正常的话,当一个客户端完成某个消息的处理之后,可以用 LREM 命令将这个消息从备份表删除。

最后,还可以添加一个客户端专门用于监视备份表,它自动地将超过一定处理时限的消息重新放入队列中去(负责处理该消息的客户端可能已经崩溃),这样就不会丢失任何消息了。