上回的 Laravel 应用开发完成上线之后,稳定的跑了一个月。业务一切正常,就这最近一周应用负载的第二台服务器总是抽风。

先说说应用的场景。

我们的应用项目代码在我们自己的服务器上,两台服务器做承载。按理来说其中一台服务器宕掉会立马故障转移到另一台服务器。但是应用前边还有两台服务器。

不太好解释,一个请求差不多是这样的:

https://www.a.com/
       | 
       | (SLB 轮询到 Server 3、Server 4 上的一台服务器,然后 301 重定向)
       ↓
https://www.b.com/abc
       |
       |(SLB 轮询)
       ↓
Server 1、Server 2(所属其他项目组)
       |
       |(Nginx 转发)
       ↓
Server 3、Server 4 (所属我们项目组)
       |
       | (Nginx 转发到 Localhost 8800端口)
       ↓
      响应

这种场景有些特殊,困于公司的各种恼人的流程,实在想不出其他法子了,以至于开发的应用适配这个架构改了好多次。

现在问题是:当 Server 3Server 4 宕机了,SLB 无法故障转移。碰巧的是 php-fpmServer 4三天挂掉了三次

我们唯一知道服务器宕掉的途径是——业务群炸了!这显然已经晚了,业务势必受到影响。

在翻看 php-fpm 日志和 nginx 日志查找了半天没有结果的情况下,心生一秒计——Supervisor 守护进程。

Supervisor 简单来说就是在你需要常驻运行的程序挂掉的时候及时拉起。这对于现在的场景是非常合适啊。

编辑 /etc/supervisord.d/php-fpm.ini,配置如下:

[program:php-fpm]
command=bash -c "sleep 1 && /usr/local/php7/sbin/php-fpm --fpm-config /usr/local/php7/etc/php-fpm.conf --pid /usr/local/php7/var/run/php-fpm.pid"
process_name=%(program_name)s
autostart=true
autorestart=true
startretries=5
exitcodes=0,2,70
stopsignal=QUIT
stopwaitsecs=2
stdout_logfile=/data/logs/supervisord/php-fpm.log

进入 Supervisor 控制台:

$ sudo supervisorctl
nginx                            RUNNING   pid 26046, uptime 0:00:01
php-fpm                          STARTING
supervisor> reread
php-fpm: changed
supervisor> start php-fpm
php-fpm: ERROR (abnormal termination)

请记住这里最后的结果是 ERROR,然而查看进程 php-fpm 确实在跑了,kill 掉进程也能拉起来,我不太清楚为什么会这样。但是跑了一天后,php-fpm 又挂了。对,是又挂了而且没拉起来。

继续锁定 ERROR,先查看 php-fpm 日志。

[01-Sep-2018 11:20:21] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:20:21] ERROR: FPM initialization failed
[01-Sep-2018 11:20:23] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:20:23] ERROR: FPM initialization failed
[01-Sep-2018 11:20:24] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:20:24] ERROR: FPM initialization failed
[01-Sep-2018 11:20:25] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:20:25] ERROR: FPM initialization failed

一直在报这个错误,看来必须要解决才行呢,看样子是端口被占用了?但是是基于 supervisor 启动的,怎么会有这种错误呢?

当然,配置是有问题的。

php-fpm 进程默认是以 daemon 方式启动的,而 Supervisor 文档 的说明是,使用 supervisor 监护进程时,被监护的进程不能是守护进程。

我们需要关闭 php-fpm 的进程守护,编辑 /usr/local/php/etc/php-fpm.conf,查找 daemonize 修改为 no

然后 killall php-fpm 的所有进程,现在查看 php-fpm 日志。

$ tail -f /usr/local/php/var/log/php-fpm.log
[01-Sep-2018 11:28:25] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:28:25] ERROR: FPM initialization failed
[01-Sep-2018 11:28:26] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:28:26] ERROR: FPM initialization failed
[01-Sep-2018 11:28:27] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
[01-Sep-2018 11:28:27] ERROR: FPM initialization failed
[01-Sep-2018 11:28:28] NOTICE: Terminating ...
[01-Sep-2018 11:28:28] NOTICE: exiting, bye-bye!
[01-Sep-2018 11:28:29] NOTICE: fpm is running, pid 26011
[01-Sep-2018 11:28:29] NOTICE: ready to handle connections

查看 supervisor 状态:

$ sudo supervisorctl
nginx                            RUNNING   pid 26046, uptime 0:00:01
php-fpm                          RUNNING   pid 26009, uptime 0:00:45

关于 php-fpm 为什么会隔三差五挂掉还没查出来,为了不影响业务,只能先守护进程保证业务正常运作。

一言 for Chrome

Github | Issues

Chrome 新标签页,基于一言 Hitokoto.cn_,简洁的黑白设计,基于 _animate.css 的过渡动画。

功能

  • [x] 点赞
  • [x] 更新
  • [ ] 自动切换
  • [ ] 设置后台
  • [ ] 搜索输入框

依赖

说明

项目下载后解压 Chrome 开启开发者模式并加载解压后的插件即可使用。如果你有更好的意见欢迎提交 issues 或者直接联系我~

有时间(等有钱了)我会打包上架到 Chrome 网上应用。

欢迎加入一言,QQ 群:70029304

起因

周五在公司加班的时候,调试了一个用户验证短信的一个 Bug,为了尽量做到接口的调用限制,对发送过的手机号设置了一定时长的「黑名单」机制,原理是将发送过的手机号存入缓存中,发送短信的时候获取缓存是否存在,如果存在就说明超频发送了。说实话很简单的一个功能,我却破天荒的调试了半天没搞明白。

后来,老大就坐在旁边,我俩一起调,从生成验证码到发送日志,全给丢到 debug 日志,挂着 tail 命令监听。

然鹅!模拟用户注册居然没有打印验证码日志,只有发送日志!

没记录日志,还始终报验证码错误,真是莫名其妙。

调试

首先,我将视线转移到缓存时间上,发现缓存时间只有 1 分钟,也就是说如果用户填写资料比较慢的话,验证码很可能就过期了!调试嘛,改到 10 分钟先。提交后更新生产环境代码。还是报错!

emmm…难道缓存系统出问题了?还是说 Redis 写不进去了?发送完验证码后,使用 Laravelartisan tinker 打印缓存,居然不是验证码,是手机号!诶,我这儿不是改了吗?(没错,刚开始的时候我勿将手机号存到 values 去了)。代码里加日志,想打印下看下什么情况。可是新加的打印日志死活打印不出来。

不行了,我要冷静下,慢慢的开始分析修改前和修改后的逻辑。

修改前:用户传入手机号->系统生成验证码(这里验证码是手机号)->将发送任务丢到队列->用户收到短信后填写->后端校验。

修改后:用户传入手机号->系统生成验证码(这里新加了日志输出)->将发送任务丢到队列->用户收到短信后填写->后端校验。

而现在的问题是:系统生成的验证码还是手机号,尽管我已经修改了;调试日志不打印!

也就是说。。队列没更新!

解决

分析出队列没更新,我立马就去查了 supervisor 的文档。之前更新队列都只使用了 rereadupdate 命令,只是更新了配置文件,但是队列并没有重新启!而 Laravel 的文档 队列 中提到:

注意一旦 queue:work 命令开始执行,它会一直运行直到它被手动停止或终端被关闭。

supervisor 是不会被关闭的,何况我给了 4 个子进程。而文档中又提到:

记住,队列处理器是一个常驻的进程并且在内存中保存着已经启动的应用状态。因此,它们并不会在启动后注意到你代码的更改。所以,在你的重新部署过程中,请记得 重启你的队列处理器.

也就是说,队列还是保持第一次执行的状态。难怪缓存值修改了没用,日志打印不出来也是这个原因。

重启 supervisor 队列命令:

supervisorctl restart laravel-queue

问题得到解决,真是被自己写的 Bug 蠢哭了。

最近公司分配了一个项目给我,时间相对充裕,所以也就直接用 Laravel 5.6 练手了。参照慕课网实战课程「Laravel 快速开发简书」和 Laravel China 社区出的 「Web 开发实战入门 (Laravel 5.5)」以及 Laravel China 社区翻译的「Laravel 5.6 中文文档」直接开怼。

上线前服务器资源成了一个问题,因为这个项目并没提到正式流程中,所有曲线救国,直接上线在官网的服务器。

配置

  • 阿里云 2 核 4 GB * 3(两台生产使用SLB,一台测试)
  • CentOS 7.4 x86_64
  • Nginx 1.12 + PHP 7.0.9

此次升级共三台服务器,两台生产环境和一台测试环境。

查看 Laravel 5.6 服务器要求 PHP 版本为 PHP >= 7.1.3,因为开发环境装的 7.1.5,所以就直接上 7.1.5 了。

评估影响范围

首先,生产环境还运行着一个官网,不过流量可以忽略不计,所以短暂的宕机是可以接受的,不然我要等到晚上才能升级。

连上服务器,根据需求判断是否需要备份之前的数据。是否安装了其他拓展(Redis、Mencache、Swoole、Yaf 等)有的话需要将 php.ini 文件备份下来。

由于官网 PHP 一直没用过,也没装过什么拓展,所以也就直接覆盖升级了。

升级

注意:是覆盖升级!并非多版本切换。

首先需要找到 PHP 老版本的安装目录。一般路径为 /usr/local/php,现在需要获取当初安装时的配置情况。

使用命令:

$ php -i | grep configure | sed -e "s/Configure Command => //; s/'//g"
 ./configure  --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-gd --with-iconv --with-zlib --enable-xml --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curlwrappers --enable-mbregex --enable-fpm --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --with-pear --with-gettext --enable-session --with-mcrypt --with-curl --with-jpeg-dir=/usr --with-freetype-dir=/usr

若提示 command not found: php 则需要进入到 /usr/local/php/bin/ 使用 ./php 来执行命令。

将以上配置给复制下来,保存好。

然后下载对应升级的 PHP 版本,这里我选择 7.1.5 如果需要其他版本的只需要在下面命令修改对应版本即可。

$ cd ~
$ wget -c http://cn2.php.net/get/php-7.1.5.tar.gz/from/this/mirror -O php-7.1.5.tar.gz
$ tar zxvf php-7.1.5.tar.gz
$ cd php-7.1.5

解压完成,现在开始配置 PHP,这时需要复制上刚刚保存的 ./configure 命令。

$ ./configure  --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-gd --with-iconv --with-zlib --enable-xml --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curlwrappers --enable-mbregex --enable-fpm --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --with-pear --with-gettext --enable-session --with-mcrypt --with-curl --with-jpeg-dir=/usr --with-freetype-dir=/usr
$ make && make install

如果命令有提示 Permission denied 请使用 sudo 来运行命令。

优化

至此,升级已经完成了。但是仍然有优化的地方。

增加或替换 /usr/bin/ 目录下相关 PHP 的命令脚本。

$ cd /usr/local/php
$ cp -f php phpize php-config php /usr/bin/
$ php -v
PHP 7.1.5 (cli) (built: Jun  5 2017 18:00:45) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

重启 PHP-FPM

现在需要将 php-fpm 重启使其生效。命令:

$ killall php-fpm
$ sudo /usr/local/php/sbin/php-fpm

运营同学有一个需求提给了我,说是每周一和周五需要到系统手动导出所有成交报告,而系统是根据城市进行分库的,导完一个城市的成交报告需要切换系统的登录城市。这个步骤异常繁琐,希望我能给个解决方案来解放他的双手。

在项目拉取了一个 hotfix 分支就开搞,通过命令触发接口导出数据生成 CSV 文件并通过附件发送给我。

测试完成,数据完整。发给运营同学准备测试数据,然后气氛有点奇怪。

运营同学:乱码。

我:试试导入?

运营同学:还是乱码。

乱码?UTF-8 还乱码?这 Office 用得啥编码格式。

搜索一通,有说是 Excel 的 Bug,可以通过加 BOM 头搞定。

BOM 头是啥呢?

在 UCS 编码中有一个叫做 "Zero Width No-Break Space" ,中文译名作 “零宽无间断间隔” 的字符,它的编码是 FEFF。而 FFFE 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 "Zero Width No-Break Space"。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到 FFFE,就表明这个字节流是 Little- Endian 的。因此字符 "Zero Width No-Break Space" (“零宽无间断间隔”)又被称作 BOM。

类似 WINDOWS 自带的记事本等软件,在保存一个以 UTF-8 编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即 BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以 UTF-8 编码。对于一般的文件,这样并不会产生什么麻烦。但对于 PHP 来说,BOM 是个大麻烦。

摘自 百度百科 BOM(Byte Order Mark)

再补充一份不同编码的字节顺序标记的表。

编码表示 (十六进制)表示 (十进制)
UTF-8EF BB BF239 187 191
UTF-16(大端序)FE FF254 255
UTF-16(小端序)FF FE255 254
UTF-32(大端序)00 00 FE FF0 0 254 255
UTF-32(小端序)FF FE 00 00255 254 0 0
UTF-72B 2F 76 和以下的一个字节:[38 / 39 / 2B / 2F]43 47 118 和以下的一个字节:[56 / 57 / 43 / 47]
en:UTF-1F7 64 4C247 100 76
en:UTF-EBCDICDD 73 66 73221 115 102 115
en:Standard Compression Scheme for Unicode0E FE FF14 254 255
en:BOCU-1FB EE 28及可能跟随着FF251 238 40及可能跟随着255
GB-1803084 31 95 33132 49 149 51

也就是说,只需要在 CSV 头部加一个 BOM 头就可以解决乱码。

<?php
    // 设置 UTF-8 BOM 头
    $bom = chr(239).chr(187).chr(191);
    $filecontents = $bom . $filecontents;
...

嗯,__记一笔,运营同学欠我一顿饭。__