分类 技术 下的文章

Laravel queue 重复执行脚本的问题

现象

有时跑一些很耗费时间的异步脚本,会在 artisan queue:listen 时通过 --timeout=3600 设置长一点的时间,以避免超时。
如果: 1. 使用 Redis 存放 queue 信息; 2. 跑了多个进程 artisan queue:listen --timeout=3600;3. 监听同一个 queue;4. Job 的时间超过 60 秒。就会发生同一个脚本被运行多次的情况。

原因

  • 在 Laravel 的框架中,通过 Redis 获取下一个 Job 是通过 Illuminate\RedisQueue 完成的。
  • 获取下个 Job 的函数是 public function pop()
  • 假设 queue 的名称为 default,Laravel 在获取到脚本后,会将它放入 default:reserved 中。 default:reserved 的结构是 sorted hash,被放入的 Job 会被赋予一个权值:time() + this->expire,默认的 this->expire = 60,Laravel 官方的配置中(config/queue.php),也默认了 60 秒。
  • 这个函数在从 Redis queue 中 pop 一个数据前,会将所有过期的 Job 放回到原来的 queue 中。
  • 过期 Job 的来源包括 default:reserved,获取所有 default:reserved 中,权值小于当前时间的 Job。
  • 于是,超过 60 秒没有执行完成的 Job 被放入了 default queue 中,等待下一次执行。

解决方案

解决方法很简单,将 config/queue.php,相应配置的 expire 设置为 null 即可。
因为 Illuminate\RedisQueue 中,如果 expire 为 null,是不会做将将所有过期的 Job 放回到原来的 queue 中的操作的。

其它

  • 这个问题在 5.4 版本还有:5.4 版本的代码略有调整,但是原理相同。
  • 如果 queue 的驱动是 Database,应该也有相同的问题。而且看了 5.2 版本的实现,还没有办法通过设置 expire 为 null 来规避。
  • expire 这个设置,和 queue 的 timeout 的配置,是冲突的。Laravel 在这点的设计上没有考虑清楚:让开发者设置 2 个可能互相矛盾的参数。而且,文档中介绍了 timeout,教开发者避免超时,没有介绍 expire,还给 expire 一个默认的 60 秒设置,让开发者掉坑。打算找个时间去提 issue~

网站被爆之后-开网站基本常识

早上点进自己做的小网站,想看看比分,说数据库挂了。当下的反应,是 TMD mongo 真不稳定。
晚上回来一看, mongo 进程果然不在,重启,网站还是各种报错。
进 rockmongo 一看,自己的表不见了,各种奇怪的字符。
去 mongo 的 data 目录下一看
string:{var_dump(md5(812812))}
肯定被黑了。
看看 mongo 和 nginx 的 log,搞明白怎么回事。
前天来一个哥们,通过 ip 爬 /pma /admin 的接口,一直没结果,昨晚估计这哥们想起来手动直接访问了 ip,竟然出来 rockmongo 的页面,而且我还没加密..

教训:

  1. nginx 的配置里,默认 444;
  2. rockmongo 这种,至少基础验证,基本的账号密码要来一个,如果真的太懒,至少限制 IP 访问;
  3. log 是好东西,自己 SB 了之后能弄明白是怎么回事。

最后,安利一下 tt-nba.com,欢迎来搞。

使用 rsyslog 将 nginx log 同步到一台机器

nginx 每天会产生大量日志,对系统和业务分析很有用。 生产环境一般有多台 web 服务器,分担压力和避免单点故障,这种情况下,很难快速定位到需要的信息在哪些机器上。
当然,有高大上的方法,比如 elasticsearch + kibana,但这些方法比较吃机器性能,有没有简单粗暴的办法呢。
如果不需要将查 log 玩出花,归并 log 到一台机器是个好办法,况且现在硬盘也很便宜。
rsyslog 是一个简单同步的工具,支持 UDP TCP,内网下很好。和 nginx log 集成的方式,有 2 种,一种是直接在 nginx 配置种写 syslog 的协议,但需要 1.7 以上版本支持;另一种,可以根据已有 log 文件的变化,做增量同步,对 nginx 版本没有限制,理论上可以同步所有 log。这次介绍的是后者。

以 ubuntu 为例,先看服务端的配置。

修改 /etc/rsyslog.conf

$ModLoad immark # 去掉前面的注视符号
$ModLoad imtcp # 去掉前面的注视符号,表示接受指定端口来的 tcp 信息
$InputTCPServerRun 10514 # 设定端口号,默认的 514 打开不了,我换成了 10514

在 /etc/rsyslog.d/ 下添加一个配置文件 40-nginx.conf

$template filename, "/path/to/log/%PROGRAMNAME%_%$year%_%$month%_%$day%.log"
$template format, "%FROMHOST% -%msg%\n"
:syslogtag,startswith,"nginx" ?filename;format
:syslogtag,startswith,"nginx" ~

第一行定义了文件名格式。PROGRAMNAME 是 tag 名称,后面会由客户端指定。
第二行定义里 log 格式,为了区分来自哪台机器,我加了 FROMHOST。要注意的是,必须在 /ets/hosts 中事说明对应 host 的名称,否则 log 中不会出现机器名称。
第三行说明 tag 以 nginx 开头的 log,以上面定义的 format 格式,存到 filename 格式的文件中。
第四行防止将 log 存放到 /var/log/syslog 中
保存后重启一下

sudo service rsyslog restart

客户端配置
在 /etc/rsyslog.d/ 下添加一个配置文件 40-nginx.conf

$ModLoad imfile

$InputFileName /path/to/nginx/access/log
$InputFileTag nginx_access_log:
$InputFileStateFile stat-nginx-access
$InputFileSeverity info
$InputFileFacility local7
$InputRunFileMonitor

$InputFilePollInterval 5

:syslogtag,startswith,"nginx" @@host_ip:host_port
:syslogtag,startswith,"nginx" ~

第一行表示从文件同步。
第三行说明需要同步的文件。
第四行说明传到服务端的 tag。
第五行是 rsyslog 用来标记文件同步位置的文件,每台客户端唯一。
InputFilePollInterval 说明同步间隔,单位是秒。
最后第二行和服务端类似,只是说明 log 上传服务端的 ip 和端口。两个 @ 符号,表示使用 tcp 协议。
保存后重启客户端 rsyslog。

这样就完成了整个配置。

好处如上所说,可以在同一个地方查看日志,现在发现一个缺点,多个服务器日志的顺序,会有错乱,原因和客户端不是及时上传 log 有关(节省网络开销),如果 nginx 写入 log 时有 buffer,也会加剧这个问题。但错位问题不严重,目前的分析中,也可以容忍错位,因此不解决了。

MongoDB shard 开启

为啥要 shard:个别 collection 数据量越来越大,又没法 archive,索引大,读取和写入性能极差。shard 可以分割数据和读写

MongoDB 的 shard,可以指定需要 shard 的 collection,这样不用所有 collection 都折腾;已经 shard 的 collection 可以取消 shard,数据会从别的 shard 传回 primary shard;可以从 replica 平滑升级到 shard。这些可以保证平滑开启。

相对于 repica,shard 多了 2 种服务:config,用于管理 shard 信息,也就是数据如何映射,非常重要,生产环境中,必须布置 3 个,有一个坏了,数据还可以正常读写,但是数据的合并和迁移就不会发生,如果所有 config 都坏了,或者 config 数据坏了,数据的读写就挂了;mongos,用于读写 shard 数据的接口服务,可以有多个,本身不保留任何状态信息,实际只有 log。

shard keys 是做 shard 前必须要决定的事情,它影响几个方面:查询,如果查询条件包含了 shard keys(必须排在前列,例如查询包含了 userId,(userId, _id) 这个 shardKey 有用, (_id, userId) 这个就没用了),则查询可以只涉及相关的 replica,否则所有 replica 都要查询;数据分割,好的 shard key 可以让数据分割的均匀,典型的例如 _id,因为每个 document 的 _id 都不一样,所以数据可以分割的非常均匀,而用户表里的性别就是一个非常差的 shard key,因为所有的数据只能分成三块(男,女,未知);存储压力分配,好的 shard key 可以将存储压力平均的分配到所有 replica 上,刚才那个 _id 在这里就是一个很差的 shard key,因为所有新数据都集中在某个分片,也就是某台机器上,这台机器压力会很大。shard key 的选择和存储的数据用法很有关系,没有一个万能的选择方案,不过任何一个介绍 shard 的书或正式文档都会讲到 shard key 的选择,可以参考。

shard 过程比较简单,首先搭起新的 mongos, config, replica 机器,连接 mongos,指定 config,首先添加原来的 replica,这样它就成为 primary shard,然后添加其他 replica 作为 shard。之后指明某个 db 可以 shard,指明某个 collection 可以 shard,指明 shard key,之后 mongo 会自动开始挪动数据,挪动过程中数据可以正常读写。