工作5年,后3年都重复着前2年?写在离职百度之际(QA篇)

最近各种原因换了个工作,回想起来校招(实习)就进百度了,一晃5年多了。最近又宣传“十四五”,也想着自己确实得好好总结下这五年,而且市面上做 QA 的比较少,希望能对新同学有些帮助。

重新review下工作

做程序员这一块,可能都会遇到第一个瓶颈,工作前2两年成长很快(特别是 QA,都是现学),但后面就慢了,感觉重复着之前的工作。这里以“捕鼠器案例”为引子(杨震原字节内部分享,来源王梦秋百度的内部分享),重新捋一下 QA 的工作:

工作第1年

刚工作接到的任务一般比较简单、直接,比如导师可能叫你去放一下捕鼠器。这个时候你一般看看文档,多观察下,就知道捕鼠器是用来捕老鼠的,你只要不给它挂在墙上就行。甚至前人已经放过捕鼠器了,你只要依样画葫芦隔一段距离放一下就行了。

这个时候你就要稍微总结下了,放捕鼠器为什么有时没捕到老鼠,你可以会观察录像,看别人的捕鼠器怎么做的,这个时候稍作改进就会效果明显。具体的到QA的工作,你现在已经非常熟悉这块的业务,每次小功能的测试都游刃有余,而且通过学习你还会了 API/UI 自动化测试,效率也变高了。

工作第2年

由于表现还不错,你已经晋升为 T4 了。这个时候你会发现只放好老鼠夹子效果还是不好,具体到工作可能会发现线上还是有很多问题。你一想,捕鼠器是干啥,消灭老鼠,但消灭老鼠,不仅仅只有捕鼠器一种方法,或者说一种方法不够。

当一个人从完成任务到解决问题,能够再问一步,这个问题关联的问题是什么,进而去提出新的问题,这就是关键的突破点。

你可能会发现老鼠药更管用。类比测试工作上来说,你引入了更多的测试方法:diff 测试、性能测试、稳定性测试、兼容性测试、安全测试、ET测试等,工作开始变的顺风顺水。

工作第3-5年

这个时候你测试环节已经做的很不错了,能负责一个系统或者业务方向的测试了,一般的话也晋升为 T5(高级测试工程师)之列了。但这个时候就开始重复之前的工作了,项目不停迭代、新功能测也测不完。而且随着需求积累,回归测试也很多了,因此对于 QA 来说这种重复的感觉比 RD、FE 更强烈

这个时候还得回到原点来,捕老鼠是为了什么?为了保护粮食。那 QA 找 Bug 是为了什么?是为了“质量”。

(图 来源百度王梦秋分享 PPT)

这个时候介绍一个在百度内部非常重要的概念:全流程质量保障。作为 QA(Quality Assurance,质量保障),自己把自己定位成测试是不行的。

全流程质量保障

可能每个公司的项目流程都不一样,但都会包含以下几部分,当你跳出“测试”来看,就会发现能做的事情还有很多: Continue Reading...

Python进/线程池使用

在 Python 中,进程/线程是个非常重要的概念,特别是 Python 还有 GIL(同一时刻只有一个线程在执行 Python bytecode)限制,使得 Python 线程并不那么好用。但 GIL 更多的是影响 CPU 密集型任务,实际业务场景更多的是 IO 密集型任务,多线程还是适用绝大多数场景。不过话又说回来,很多时候不太好判断是 IO 密集型多还是 CPU 密集型多,需要在多进程、多线程环境下分别验证。

但多线程和多进程手写起来还是有点差别,好在 multiprocessing.Pool 提供了统一的接口,可以无缝切换:

下面介绍下 Pool 类如何使用。

首先是构造函数:

class multiprocessing.Pool([processes[, initializer[, initargs[, maxtasksperchild]]]])

接收 processes initializer initargs maxtasksperchild 4个参数:

processes,就是池里有多少个进程,可以不传,默认 CPU 个数,可以按需多设置几倍

initializer、initargs,如果设置了参数,则会在每个进程初始化的时候调用 initializer(*initargs)。这个非常有用,比如可以在初始化的时候建立连接,连接重用。

maxtasksperchild,用于设置每个子进程执行多少个任务后重启。虽然很简单粗暴,但这是防止内存溢出、资源未释放等问题的常见手段,类似 PHP-FPM 的 pm.max_requests 参数。线程池无此参数。

这里有一个 hack 技巧,将初始化的数据库连接,绑定在了请求函数上,这样调用请求函数发送请求时,就不用重新建立连接,直接使用即可。

我们一般使用线程池有两个场景,一是关注执行结果,比如我们并行去 redis mysql 各个地方请求数据,然后整合这些数据,二是不关注执行结果,比如新开一个线程打印一个请求的审计日志,不阻塞主进程返回数据。这里主要是介绍下 apply 和 apply_async 的区别,其它的都类似。

apply(func[, args[, kwds]]),主要是传一个执行函数和参数,阻塞并得到返回结果。

apply_async(func[, args[, kwds[, callback]]]),也是主要传执行函数和参数,但返回的是一个 multiprocessing.pool.AsyncResult 对象,AsyncResult 主要有 get([timeout])、wait([timeout])、ready()、successful() 4 个方法,都很好理解,用的比较多的是 get 方法,给定超时时间内获取执行的结果,如果超时抛出 multiprocessing.TimeoutError 异常。如果 timeout 是 None,则一直就等待,行为就和 apply 一致了,实际上 apply 也是调用的 apply_async get:

apply_async 还有一个有用的参数 callback,相对于异步回调了,一般用于上述的场景二。下面是一些示例:

运行结果:

注意代码的最后一行,这里不仅仅是为了看回调结果,还因为回调是回调到主进程执行,如果主进(线)程已经退出了,那就处理不到回调了,实际使用需要注意运行环境。

最后,multiprocessing 模块的 pool 功能只是其中很小的一部分,但比较实用,后面有新的心得再介绍其它功能。

参考资料:

https://docs.python.org/2.7/library/multiprocessing.html#module-multiprocessing.pool

Nginx+Tomcat偶现502分析

问题

业务为了负载均衡,前面放了个 Nginx,但最近 502 报警有点频繁,影响了 SLA,因此对这个问题做了较深入的研究。

502 Bad Gateway

简单来说就是 Nginx 找不到一个可用的 upstream,可能的原因有:

  1. 压根是配置错误
  2. 连接 upstream server 发生错误/超时
  3. upstream server 到了处理瓶颈

还有一个重点是轮询了 upstream server 后任然没有一个可用的。但是不管什么原因,都能在 Nginx 的 error log 中找到报错详情。

upstream prematurely closed connection

在 Nginx 中找到错误日志(开了 debug):

看了下 Nginx 代码,发现是 c->recv(); 读到的内容为 0,日志中也有显示 recv: fd:65 0 of 4096,说明没有获取到 response

同时也查了 Tomcat access 日志,请求还没到 Tomcat。看情况就像日志里面说的,连接被断掉了。

一开始怀疑是网络原因导致连接断掉了,但出现较频繁,期间内网也无网络故障,应该和网络无关。

再看日志发现报错前后有很多 keepalive 信息,尝试关掉 keepalive,502 就没了,但这会对性能有一定影响,感觉有点因噎废食了,还得继续研究。

keepalive

HTTP 持久连接(HTTP persistent connection,也称作 HTTP keep-alive 或 HTTP connection reuse)是使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。

一般 Nginx 会配置 keepalive 以提高性能:

Syntax: keepalive connections;

Activates the cache for connections to upstream servers.

The connections parameter sets the maximum number of idle keepalive connections to upstream servers that are preserved in the cache of each worker process. When this number is exceeded, the least recently used connections are closed.

可以配置每个 worker 对 upstream servers 最大长连接数量。同时这个长连接受 keepalive_requests(默认100) 和 keepalive_timeout(默认60s)配置的影响。

但 keepalive 也有缺陷,会加重 webserver 的负担,因为需要绑定一定数量的线程或者进程来维持长链接。注意 keepalive 并没有连接复用(即同一时间窗口不能处理多个请求,这个在 HTTP/2 中才实现),仅节省了新建/关闭连接的开销,类似连接池了。所以 webserver 一般都有类似 nginx 的 keepalive_requests、keepalive_timeout 配置,让空闲的连接断掉。

查看了 upstream(tomcat) 配置的 timeout 是20s,lighttpd 的 requests 是16,timeout 是 5s,都远小于 nginx 的配置。

原因就清楚了,upstream servers 先断了 keepalive 的长连接,但 nginx 仍使用了这个已经断掉的连接。

至于 nginx 为什么不主动检测一下连接是否可用呢?猜测应该是性能原因,一直检查连接池中的连接是否可用没必要,keepalive 协议本身也没业务心跳啥的。PS:商业版的 ngx_http_upstream_hc_module 可以主动监测(世界加钱可及)。

proxy_next_upstream

那么 Nginx 如何保证高可用呢?答案是重试。这就涉及另一个重要配置 proxy_next_upstream,在什么情况下进行重试,默认为 error timeout。按理说我们这个场景应该会重试,但没有重试。这是因为 Nginx 较新版本(1.9.13)多了个 non_idempotent 配置,默认 POSTLOCKPATCH 等请求不重试,因为这些操作是非幂等操作,会对服务端数据造成影响(比如发消息接口,重试可能会重复发消息)。日志中也发现 502 的全都是 POST 请求。

要完全解决这个问题就需要对 proxy_next_upstream 配置簇深入看看,我们先做个测试。 Continue Reading...

Windows 10生产力提升之WSL实践

Mac 和 Windows 哪个更好,估计还能再吵几年。抛开部分硬件上的差距,Windows 用作开发上确实有点不顺手,毕竟大部分后端程序都是运行在 Linux 服务器上,macOS 占有先天优势。之前都是通过在 Windows 上起虚拟机来解决这个问题,如今 Windows 10 推出的 WSL(Windows Subsystem for Linux),成为了新的选择。

WSL 的官方介绍

The Windows Subsystem for Linux lets developers run GNU/Linux environment - including most command-line tools, utilities, and applications - directly on Windows, unmodified, without the overhead of a virtual machine

简单来说就是不需要修改,无需虚拟机,就可以在 Windows 上直接使用 Linux 环境(grepsedawkd等),运行 Linux 程序(vim, MySQL, Apache等)。这就很舒服了,小孩子才做选择,大人全都要,实际如何我们来实践一番。

安装

首先是按照,非常简单,开启子系统功能后在应用商场选择一个发行版的 Linux 安装就行。官方指南:https://docs.microsoft.com/en-us/windows/wsl/install-win10

启动之后就可以使用了,你可以根据自己使用习惯,做一些初始化的设置(oh-my-zsh啥的)这里就不细说了。WSL 默认是通过安装的应用进入的(直接运行 bash.exe 也行),但大家还是喜欢通过 Xshell 等工具进入了,这就需要做一些配置。WSL 和 Windows 本机是端口共用的(这点特别注意,防止程序端口冲突了),直接通过 127.0.0.1 就能连接上,剩下的就和直接使用 Linux 差不多了。

但是,等你重启电脑,再使用 Xshell 连接时发现连不上了,没有 sshd 进程了,每次都需要打开 WSL 程序然后再运行 sudo service ssh start 有点麻烦。这就是 WSL 不一样的地方了,WSL 不是真正的一个系统,没有开机启动这一说,把 sshd 设置成开机启动也没有用。既然不能加入到 WSL 的启动项,那就加入到 Windows 的启动项中:

编写 run_wsl.vbs 脚本,用来在 bash.exe 中启动 sshd,然后将脚本放到系统启动目录里面(WIN+R 运行 shell:Common Startup 或者 shell:startup)就行了。

不过需要注意 sudo 需要输入密码,还得把密码关了。WSL 中运行 sudo visudo 增加一行:ubuntu ALL=(root) NOPASSWD: ALL,表示 ubuntu (换成自己的)这个用户使用 sudo 运行任何命令时都不用输入密码。

当然安装之后可能会遇到一些其它问题,主要是一些安全软件和 WSL 不兼容,比如卡巴斯基会让 WSL 无法联网,腾讯的 TGP 会影响绑定端口,遇到了只能暂时关掉相关软件,相信之后这些问题会越来越少。

使用 Redis

之前在 Windows 上使用 Redis 比较麻烦,有一个 Windows 版的 Redis 但已经很久没有更新了,还有就是选择虚拟机。现在直接在 WSL 里面编译安装(make & make install)就行了。写好配置文件,sudo redis-server /etc/redis/redis.conf 运行即可。

不过有些同学可能会像我之前把 Redis 注册为 systemd 服务,但发现会报错:System has not been booted with systemd as init system (PID 1). Can't operate 说的也比较明白,系统不是通过 systemd 启动的。其实 WSL 不是一个真正的系统,应该说是一个容器,类似 Docker(这么一说感觉微软野心好大)。

LXSS diagram

(图片来源:https://docs.microsoft.com/zh-cn/archive/blogs/wsl/windows-subsystem-for-linux-overview

在 WSL 中运行的程序是真正的 Linux 二进制文件,不是移植版,不过当程序由用户态切换到内核态时lxss.syslxcore.sys驱动将会将 Linux 的系统调用翻译为  NT APIs 来模拟 Linux 内核。简单来说 WSL 是将 LInux 底层 API 用 NT 实现了一遍(所以 WSL 中没有 Linux kernel 代码),而且有些操作还是 Linux 独有的,比如fork(),可想而知这里面工作量有多大,任重而道远。

言归正传,Redis 已经在 WSL 中启动了,我们试试在 Windows 上能不能连上:

完美,就和 Windows 本地装的 Redis 一样。

与IDE集成

虽然说大部分语言都是跨平台的,但是还是有很多差别,比如 Python 的 epoll 只能在 Linux 下使用。在 Windows 上开发不是很方便,没有智能提示,还需要上传到服务器运行、调试。有了 WSL,现在可以在 Windows 上开发,然后直接在 WSL 里面运行。 Continue Reading...

phpMyAdmin浏览数据页面卡死

我们使用phpMyAdmin经常是一进去就直接展开数据库,点击表名查看数据,这样可以清楚的了解表的结构方便写 SQL。默认情况下是显示 25 条数据,即使有几亿行也很快。

但最近我换了一个 read 账号登录,点击表名的时候直接卡死了

应该是查询卡住了,命令行连上数据库,show full processlist发现有个查询确实卡住了:

一开始只关注到状态是Creating Sort Index,再结合使用 write 账号登录打开却很快的现象,认为是 read 账号没有 create 权限,导致卡着了(没有权限的话应该是直接报错的)。但是查了下 read 账号其实给的就是全部权限,一度陷入了僵局。

调整心态,回到原点,还是从「使用 write 账号登录打开却很快」的现象出发,查看日志,发现 write 账号进入的话点击表名浏览数据使用的 SQL 没有ORDER BY,那就是自动加的,查了下,phpMyAdmin 会自动保存上一次对字段排序的操作,再次操作字段能取消排序,但问题是我现在已经无法进入浏览页面了。

那考虑是不是能在哪里设置,关闭这个。页面上有个浏览模式设置,但这个试了下好像没啥用。

还是看下官方文档吧,找到了设置项,需要把$cfg['Servers'][$i]['table_uiprefs']$cfg['RememberSorting']设置为false。这个功能还是好的,但在多人使用同一账号,数据量又比较大的情况下,还是关闭比较好,关闭也不影响排序功能,只是排序的操作不会保存下来。

Since release 3.5.0 phpMyAdmin can be configured to remember several things (sorted column $cfg['RememberSorting'], column order, and column visibility from a database table) for browsing tables. Without configuring the storage, these features still can be used, but the values will disappear after you logout.

然后还得把已经保存的设置项清除掉,位置在数据库:phpmyadmin(默认)表:pma__table_uiprefs,把这个表清空即可又回到之前丝滑的感觉了。

参考资料

https://stackoverflow.com/questions/33809816/phpmyadmin-automatically-adding-order-by-to-browse

https://docs.phpmyadmin.net/en/latest/config.html#cfg_Servers_table_uiprefs

tomcat设置remote JVM debug后stop失败

最近突然出现执行 tomcat/bin/shutdown.sh 停止 tomcat 失败的情况,报错如下:

看了一下 catalina.sh: line 365 就是 stop 那里执行失败了,然后调用系统的 kill -15 杀死进程

再仔细看看报错,发现有端口被占用、dt_socket failed to initialize 等提示,觉得应该是加了 debug 命令的原因,去掉就好了。

但这个感觉也不太好,不能因噎废食呀。搜了下相关情况,发现是 JAVA_OPTS 用的不对,应该用 CATALINA_OPTS,因为 CATALINA_OPTS 变量在 stop 的时候不会被使用(如上图)。

所以最优的做法是使用 CATALINA_OPTS 变量设置 remote debug 相关参数。

 

参考资料

解决tomcat shutdown时的地址被占用问题,https://my.oschina.net/u/1770666/blog/370620

https://stackoverflow.com/questions/11222365/catalina-opts-vs-java-opts-what-is-the-difference

一个诡异的 Chrome 视频播放(加载)问题

今晚上线了一个新的 web 页面,主要多了一个背景视频功能,大概是这样:http://thenewcode.com/samples/polina.html 。但上线后遇到一个问题,HTTPS 下背景视频很大概率不播放。线下测试的时候遇到过类似的问题,是因为 webserver 没有在 mime.types 文件添加video/mp4    mp4 mp4v mpg4浏览器不能识别的原因,但线上确定不是这个原因。

Chrome 抓包发现是视频文件请求 412 了,单独访问这个视频文件也是 412 偶现不能访问,但 http 下却没有这个问题。查了下 412 是连接建立先决条件失败,第一次遇到这个错误码,直接看状态码定义,看不出个所以然。看起来自己一时半会儿解决不了呀,马上把这个问题抛到公司的群里,一会儿得到了几个大神的回复:很大可能是 If-Match 和 ETag 对不上的问题。仔细看了下 412 的请求 headers 部分确实是这个现象。

可以看到,Chrome 在加载一个视频的时候是分段加载,当某一个请求 412 的时候会造成整个视频加载失败。群里的璐神给了一些参考资料,里面有一段说明:

在大型多 WEB 集群时,使用 ETag 时有问题,所以有人建议使用 WEB 集群时不要使用 ETag,其实很好解决,因为多服务器时, INode不一样,所以不同的服务器生成的 ETag 不一样,所以用户有可能重复下载(这时 ETag 就会不准),明白了上面的原理和设置后,解决方法也很容易,让 ETag 后面 二个参数,MTime 和 Size 就好了。只要 ETag 的计算没有 INode 参于计算,就会很准了。

觉得这个说得很有道理,决定改一下 Apache 服务器试试。看配置文件的时候发现,http 的配置下有一行 FileETag -INode -MTime Size 的配置,但是 https 下没有,把这一行也加到 https 的配置文件中果然就行了。

虽然问题是解决了,但是感觉还是没有找到根本原因的样子。看了下 Apache 的文档发现 FileETag 默认使用 MTime 和 Size 两个参数,因为集群部署代码时是串行的,且会改变文件的时间(OP 部署系统的锅,中间有一步进行了 cp 操作),所以应该是 MTime 不一样造成了每台服务器上应该是同一个文件的 ETag 不一样,而每个分片请求落在了不同的机器上,造成If-MatchETag对不上,不是之前猜测的 INode 问题。但感觉还是哪里不对,为什么If-MatchETag对上了的请求就没问题了,按照理解ETag是用来做缓存的,对不上很正常,web server应该返回完整内容呀。下班回家又仔细想了一下,觉得这个问题应该反过来思考,因为 Chrome 是分段加载视频,需要保证这个视频在加载过程中是没有变动的,这样才有意义,所以需要每次请求都对比 ETag。那么为什么其它浏览器没这个问题了,抓包发现 IE 没有分段下载功能,是当一个文件都加载完成后才播放。FireFox 没有带上 If-Match 的请求头:

Safari 也没有使用 If-Match 的请求头,因此就 Chrome 下有这个问题。If-Match 的说明也验证了我的猜想:

For GET and HEAD methods, used in combination with an Range header, it can guarantee that the new ranges requested comes from the same resource than the previous one. If it doesn't match, then a 416 (Range Not Satisfiable) response is returned.

当然我这个案例中错误码是 412(通常用作 PUT 方法更新资源出错时),不过这是一个意思,符合预期。

ETag 工作流程和生成算法补充:当客户端第一次向服务器请求资源时,服务器会返回资源并根据资源的一些信息生成 ETag 返回给客户端,客户端不需要了解 ETag 是如何生成的,只需缓存下 ETag 当下次请求时带上(If-None-Match: ETag)。表示如果这个资源的 ETag 和我请求的不一样则返回数据,一样则返回 304。

不同个 webServer 生成 ETag 的算法可能不一样,比如上面提到的 Apache 使用的是 INode MTime Size 三个文件属性计算而得:

参考资料:

Apache配置Etag详解 https://www.yudouyudou.com/jiaochengheji/wangzhanjianshe/262.html

https://httpd.apache.org/docs/2.4/mod/core.html#fileetag

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match

https://web.dev/http-cache/

https://stackoverflow.com/questions/44937/how-do-you-make-an-etag-that-matches-apache

Ubuntu 16.04 上安装 MySQL 5.7 的一些细节

最近腾讯云折扣力度比较大,在加上之前 DigitalOcean 上的 VPS 访问不是太顺畅,就把服务器迁回了国内。重装了一把 MySQL,遇到了一些细节上的问题,觉得很有意义,记录下。

首先使用 MySQL 官方的源安装,这个在一年前的《Ubuntu 平滑升级 MySQL 到 5.7》中描述过,具体过程不再赘述。说一些细节上的东西:

1. Ubuntu 16.04 要求 2017.01.01 后所有的源必须使用 SHA2 以上版本的摘要签名算法(https://wiki.debian.org/Teams/Apt/Sha1Removal)。目前来说具体表现是在执行sudo apt-get update时会显示一个警告“W: http://repo.mysql.com/apt/ubuntu/dists/xenial/InRelease: Signature by key A4A9406876FCBD3C456770C88C718D3B5072E1F5 uses weak digest algorithm (SHA1)”。访问一下网址确实看见使用的是 SHA1 签名,这个只能等待官方修复。

2. 安装过程中会要求设置 root 密码,这里你可以你直接回车(设置空密码),但实际上是给你创建了一个 auth_socket 的 root 用户,并不是空密码。

这时你登录的话需要使用 socket 方式连入:sudo mysql --protocol=socket --socket=/var/run/mysqld/mysqld.sock,注意这里需要用 root 权限运行,保证有足够权限读取 sock 文件,不然会默认使用当前用户进行传统的 ip+port 登录,这样肯定是登录不上的。登录上去就可以改 root 密码了,正常执行ALTER USER 'root'@'localhost' IDENTIFIED BY 'test';后发现密码并没有被改变(会报 warning),观察上面的截图会发现 root 账号的认证插件用的是auth_socket,我们需要这样执行命令ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'test';。就能回到传统的账号密码认证了。

3. 细心的同学可能已经发现了,多了一个mysql.sys的用户,按照官方的说法这是给 DBA 使用的,防止直接使用 root 账号带来的重命名和误删账号的问题。这个用户默认有一个初始密码,但在日志里面没找到(源码编译 MySQL 5.7 后 mysql_install_db –initialize 时会把密码记录到日志中)。这里索性重新设置一个密码(ALTER USER 'mysql.sys'@'localhost' IDENTIFIED BY 'test';)。

到这儿基本上已经安装完成了,但若你细心的话会发现在启动日志里面会有一些警告:

1. TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details). 这个是说explicit_defaults_for_timestamp缺省值已经废弃了,需要显式的在 my.cnf 设置 explicit_defaults_for_timestamp = 1

2. [Warning] Could not increase number of max_open_files to more than 5000 (request: 65535) 这是个常见的错误,一般修改 /etc/security/limits.conf 即可,但改了之后好像不奏效。这是因为在 /lib/systemd/system/mysql.service 配置中还有参数 LimitNOFILE 限制,把这里也修改了就可以了。

3. 还有日志中时间的时区和系统的不一致,看日志不方便。这是 5.7 新加的参数log_timestampshttps://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_log_timestamps),在 my.cnf 设置为 log_timestamps = SYSTEM 即可。

全部搞定后建议执行sudo mysql_secure_installation命令,做一些安全设置,比如禁止root远程登录、删除匿名用户、验证密码安全级别等。

最后介绍一个比较好用的命令:mysql_config_editor,解决命令行登录MySQL便捷性和安全性的问题。我们为了本地登录MySQL方便经常会写一些一键登录的小脚本。基本上都是直接执行 mysql -uroot -p'test' 命令,这样会带来一个问题,执行的命令会被记录在bash_history中,当然你也可以禁用bash_history的功能,但这样会失去一些便捷性,也不大方便追查历史的问题命令。而且在新版本中直接使用这种方式登录会有警告信息。

这时我们就可以使用 mysql_config_editor 来创建一个 login-path,登录时直接使用mysql --login-path=xxx即可。一个典型的创建指令:mysql_config_editor set --login-path=xxx --host=localhost --user=root --port=3306 --password,然后输入密码。这里需要注意一下,如果粘贴的密码中有特殊字符会有问题,需要加上引号。比如你的密码是 test!@#,需要复制文本 'test!@#' 进行粘贴。创建的文件默认在 ~/.mylogin.cnf,不过这是个二进制文件,看不出来啥(比较安全)。我们可以使用 mysql_config_editor print --all 命令来看看已经创建的 login-path:

这里也能看见密码是加密存储的,在一定程度上保证了安全。而且 .mylogin.cnf 文件可以分发给使用同一个MySQL-client的用户,提高了安全性也方便运维管理。很实用的功能,强烈推荐使用。

MySQL 每个版本都有很多安全性和性能的改进,值得去学习、研究。而且 MySQL 8(现在都流行跳版本)也快出来了,前景更是一片大好。

 

参考资料:

Change user password in MySQL 5.7 with “plugin: auth_socket”, https://www.percona.com/blog/2016/03/16/change-user-password-in-mysql-5-7-with-plugin-auth_socket/

Where is the MySQL 5.7 root password?, https://www.percona.com/blog/2016/05/18/where-is-the-mysql-5-7-root-password/

Do I need to change the mysql.sys password, https://askubuntu.com/questions/824139/do-i-need-to-change-the-mysql-sys-password

Can not increase max_open_files for Mysql max-connections in Ubuntu 15, https://stackoverflow.com/questions/30901041/can-not-increase-max-open-files-for-mysql-max-connections-in-ubuntu-15

一个css选择器的小坑

之前博客主题中的有序列表显示有点问题,列表标记位置会在正文左边突出一部分,所以设置了 padding 值来解决此问题:

但有发现无序列表也有这个问题:

2016-05-10_015434

所以想当然的在之前的位置后面把 ul 标签加上:

然后就悲剧了,页面上其它位置的 ul 也 padding-left: 20px 了:

2016-05-10_020412

好久没写 css 了,百思不得其解,试过 .article-content > ol, ul.article-content section > ol, ul 都不行,甚至怀疑同级标签也有继承的关系。后来发现其它地方会把 class 标签写两次才恍然大悟,应该是逗号「,」优先级更高一点,把选择器解析成了 .article-content ol 和 ul 两部分,所以选择器写成 .article-content ol, .article-content ul 就可以了。

不用就会忘,随便改点东西就踩坑了。Orz!

Nginx+PHP-FPM 500 504错误简析

最近新配置的 Nginx + PHP-FPM 环境遇到了几次 Nginx 500(Internal Server Error) 和 504(gateway timeout) 错误。就索性把配置修改的过程记录下来,已备后查。

先说一下 500 错误,这个一般是 PHP 代码错了,看一下 PHP 的 error 日志就清楚了。但有时候是 Nginx 和 PHP-FPM 配置不当导致的,比如说上传文件的场景。我们都知道 php.ini 文件里面 post_max_size 和 upload_max_filesize 两个配置项决定了文件上传的大小。但上调配置后发现大文件上传还是会 500,原来是因为 Nginx 也会限制上传文件的大小,有 client_max_body_size 配置项决定。这里还有一个比较重要的参数:client_body_buffer_size。为了节省资源 Nginx 不会把 request body 里面的数据一口气读到内存中,而是分片处理,这个参数就是设置缓存区的大小(一般也不用改),大于缓冲区大小的数据会以一个个小文件(buffer_size)形式存储到 client_body_temp_path,为了避免高并发下缓存文件冲突和一些文件系统限制(比如 ext3 磁盘格式文件夹下一级文件个数限制为 3.2w 个)以及提高性能可根据业务需要设置多级子目录。还需特别注意的是路径要有被 Nginx 进程写的权限,不然又会 500 错误了。

然后是 504 错误,除了业务代码本身超时更多是配置问题。还是先说 PHP 的配置,很多人都知道 PHP 脚本有执行时间限制(php.ini 中的 max_execution_time),但在 PHP-FPM 却是 php-fpm.conf 中的 request_terminate_timeout 配置项控制的。还有就是 max_input_time 配置项,值比较小的话在接收大文件或大数据量 POST 请求的时候可能会超时,一般不用做改动,因为大部分场景都是上游 web server 接收好数据后传给 PHP-FPM 进程的,传输的非常快,几乎不会超时,但也不能一概而论,要看具体业务场景。然后就是 Nginx 的锅了,刚也说到是 Nginx 接收好(或者说是转发)数据后传给下游,在这个承上启下的环节很容易超时。几个比较重要的配置项:fastcgi_send_timeout 控制向下游(这里是 PHP-FPM)发送数据的超时时间,与之对应的一个配置项是 fastcgi_read_timeout,表示读取下游 response 的超时时间,这里需要注意的是要和前面说的 request_terminate_timeout 保持一致(或大于)。还有一个配置项 fastcgi_connect_timeout,表示和下游 FastCGI 建立连接的超时时间,不过这个值就不用设置的太长了,不然下游某个节点挂了就会夯住 Nginx 的处理线程,影响业务。

我所遇到的场景就是上面这些,可能还有其它一些情况,具体问题具体分析应该也很好解决。

参考资料:

https://nginx.org/en/docs/http/ngx_http_core_module.html

https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

《PHP-CGI 进程 CPU 100% 与 file_get_contents 函数的关系》 http://zyan.cc/tags/request_terminate_timeout/1/

《PHP超时处理全面总结》 https://segmentfault.com/a/1190000000313184

《PHP file upload affected or not by max_input_time?》 https://stackoverflow.com/questions/11387113/php-file-upload-affected-or-not-by-max-input-time

本 Blog 不支持评论,如有疑问或建议请联系我,以完善内容,期望帮助到更多的同学