LEN

千万级用户登陆打点队列堵塞问题总结
今天周六, but 996的我依然上班, 白天没什么事情. 傍边同事最近在搞登陆打点数据落地的脚本.简单介绍一下场...
扫描右侧二维码阅读全文
23
2017/07

千万级用户登陆打点队列堵塞问题总结

今天周六, but 996的我依然上班, 白天没什么事情. 傍边同事最近在搞登陆打点数据落地的脚本.

简单介绍一下场景:

千万级社交App, 用户登陆打点, 根据用户所属城市地域进行划分为32地区, 根据登陆用户所属地域ID落地用户登陆信息到Mysql表.

_用户登陆打点数据由服务器组提供RPC接口. PHP负责数据落地_.


介绍PHP落地逻辑

使用两个脚本, queryUserInfo 和 setLoginData :

queryUserInfo :
   使用服务组提供的RPC接口获取用户登陆数据, 拿到数据后判断是否为空, 为空则休眠0.1秒 usleep(100000)
    不为空拿到数据, 使用UID执行两个操作:

       1) 请求内网http接口拉取用户参数 (坑 后面聊)
       2) 从redis中提取用户数据

    上面的操作ok 后整理数据 LPUSH 写入 redis队列(单一实例). 设计是根据用户所属地域ID划分32片, 后端脚本setLoginData 针对没分片队列RPOP.

setLoginData :

    根据分片ID RPOP 判空休眠0.1秒, 数据ok整理数据落地 Mysql. (每次仅Insert 一条登陆数据 坑 后面聊)

主要就是围绕这两个脚本进行优化处理, 在这里将queryUserInfo 命名为前端脚本, setLoginData 落地脚本.


遇到问题

今天遇到的问题很多, 一个一个描述吧. 实际上都是一环套一环的. 连锁问题导致bug:

1.过去的几天服务器组提供的PB数据一到用户登陆高峰期就发生堵塞, 对于PB的数据及结构同事并不清楚. 服务器组提供了一张图表, 因为在家无法登陆后台截张图. 简单说就是用户登陆每分钟的队列堵塞走势图. 这张走势图告警阀值是200W. 前天堵到了接近900W.

    分析下定位读取PB数据能力太差. 阅读代码, 发现每次提取数据是1000条用户数据, 为了定位加入日志捕捉pb读出的数据. 看到日志心都凉了... 写法是提取1000 But 到手仅有1条. 前天高峰期通过日志计算单一脚本读取能力在每秒90次. 也就是说当个脚本每秒仅能提取90条数据. 脚本设计上是单一脚本能做到每秒提取3k+/秒, 显然有问题. 同事和服务器组的撕逼, 得到接结论是获取类型的错误(没记住), 服务器组告知批量获取数据最大是2kb 也就是6条记录. 预想这效率回提升很大, 但是计算出的每秒读取能力下降为 每秒20-30 次 还很不稳定, 每秒取出的数据 120-180 条之间 读取能力提升近一倍. 但对于php 而言还是太慢了. 如果想要消除PB数据任重道远. 我们后端脚本使用supervisor对php脚本进程进行管理. 当时两台生产服务器不舍相同数量任务后端落地脚本32个进程, 前端脚本18个进程 * 2台服务器 32及进程高分期PB 近900W一点都消不下去...

alt

通过 nohup 临时添加脚本消除队列, 饮鸩止渴而已... 脚本需要优化!

   nohup /usr/local/php/bin/php  /data/webdir/login_user/index.php userRecord queryUserInfo &

又起了50个进程, 才算是吧900W 慢慢消下去了...

看过之前 队列堵塞问题总结 的朋友应该知道前阵子, 我的生产环境队列堵了 4000W. 所以从上次事件中学习到很多东西. 这次都可以用到了. 这次就不需要在求人了 .

我建议同事将脚本逻辑改下. 前端脚本仅做PB数据的消费者, 不放置过多逻辑. 将逻辑处理放置后端落地脚本处理. 前面留了个坑就是 请求内网http接口, 虽说是内网http接口不存在环境恶劣问题, 但是显然redis读操作和curl 请求接口相比,当然是请求接口占用了前端脚本一次操作的大量时间(第三方接口不可控). 两项逻辑去除后, 再看脚本执行效率果然有所提升, 但多条提取PB数据速度竟然比单条提取PB数据还慢.... 最后是改为单条提取PB数据, 单个进程速度提升至1500+/秒. 嗯 第一个问题解决了. (快速消费PB数据)

2.第二个问题发现时我的同事已经坐上了回老家的火车. 因为在修改前端脚本及后端脚本后, 为兼容supervisor守护的脚本进程, 所以更换了新的queue key 原来的是 user:login:info:%d 1到32 现在仅使用一个队列 user:login:info:queue, 不在做分片处理. (都是从一个redis实例中读取数据, 队列分不分片没太多意义). 嗯 我要说的是找运维对队列进行监控 阀值2000 . 诶我当时想的太好了. 因为一直都是前端脚本提取速度更不上PB数据写入速度, 所以后端落地脚本是基本没有压力的! 但是后端落地脚本, 经过添加两项逻辑后自身执行单次任务效率已然降低, 这是我没考虑到的问题, 最初 llen user:login:info:queue 仅堵塞2w 数据, 当时是下午5点用户登陆开始激增的时候. 我看到队列堵了下意识认为是后端落地脚本启动进程太少RPOP的不够, 就nohup启了几个看到有效果 :

 nohup /usr/local/php/bin/php  /data/webdir/login_user/index.php userRecord setLoginData &

持续监控队列长度

watch -n 0.5 redis-cli -h 10.xx.xx.xxx -p xxxx -n 0 llen user:login:info:queue

过了几分钟后队列长度又开始增长这次是疯涨, 一秒几千, 我一直nohup 启后端落地脚本, 两台服务器共计130个进程执行后端落地脚本, 仍然消不下去...
使用monitor查看redis实例操作将大概10秒左右输出打入tmp.log


redis-cli -h 10.xx.xx.xxx -p xxxx monitor >tmp.log

分析每秒中的lpushrpop操作数:

tail tmp.log
cat tmp.log |grep 1500745289 |grep -i lpush |wc -l 
cat tmp.log |grep 1500745289 |grep -i rpop |wc -l 

结果很差劲 lpush 2500+/秒 rpop 500+/秒 后端落地脚本130个仅有RPOP , 看的这个结果.我开始检查后端落地脚本.

后端落地脚本加入新逻辑后一开始是没问题的, 随着用户登陆量增多发生了队列堵塞, 检查日志并不是休眠引起的. 落地脚本中http接口和mysql写操作我认为是最降低执行效率的. 旁边同事提醒我是不是 Mysql连接使用的不是单例. 查看之下果然如此. 简单将mysql连接改为单例后. 重启脚本.
啧啧. 果然消下去了, 当时队列已经堵了250w 用了几分钟队列就消下去了.
分析下原因, 由于脚本中调用Mysql实例并不是单例, mysql自身设置有max_user_connections 最大用户连接数, 每次执行都会重新创建一个mysql连接, 单次执行结束后并没有释放连接. 没多久就把mysql 连接资源耗尽了. (解决脚本mysql单例问题)

3. 下午7点 旁边同事下班了. 我要等人所以没啥事就在公司呆着顺便看下高峰期PB的数据前端脚本能否抗住.
实事证明前端脚本改得很成功! but 后端落地脚本又有问题了. 这次还是老套路nohup 多启了20个后端落地脚本. 然并软啊. 还是鉴定不移的增长. 再次计算redis实例每秒中的lpushrpop操作数: lpush 4300+/秒 rpop 3600+/秒 . 后端落地脚本RPOP没有LPUSH 给力.. 我后端落地脚两台生产环境共计启动100个进程去RPOP 速度仍然没有改善, 队列仍然在堵.
最后定位原因是mysql的锅 前面 (每次仅Insert 一条登陆数据 坑 后面聊) 这个坑就是单条插入mysql非常浪费吸入资源. 修改代码为每50条记录拼装 insert (field)values($field1),($field2).... 改为批量插入后能提发幅度提升, 但仍存在问题. 如果重启脚本, 会丢失保存至内存中的记录. (单条insert 替换为 批量insert)

持久化的脚本均使用supervisor守护进程. 队列消除后kill nohup 进程

ps -aux |grep setLoginData |grep admin |grep -v grep |awk '{print $2}' |xargs kill -9

PB数据昨天修复bug后得到一个大致统计. 下午16时前每秒大致1k/秒不到. 之后开始逐渐增量, 晚8点高峰期峰值6k+/秒. 10点后均值2k+/秒.

总结一下 :

上面的问题文字描述比较苍白, 还需生产环境下遇到后才能理解.

1) 脚本业务逻辑优化, 解决PB数据堵塞问题.
2) php脚本中DB创建连接及其他服务建立必须使用单例模式.
3) 脚本中Insert Mysql 批量插入性能更加.

描述的问题不是真准确, 重点在于发现并找到问题的过程. 欢迎撕逼
alt

2017/07/25 后续问题总结. 最近几天21点至22点 用户登陆高峰期 队列堵塞2W阀值 报警.

监测队列 lpush/rpop ops数 总ops 最高才8K/s 但是rpop最高不过3K/s 速度完全跟不上. 增加后端落地脚本并没有改善. rpop 没有改变.

定位问题出在落地脚本的 http接口 响应速度, 诶写了个测试接口脚本. 这个http接口 每秒响应110次... 落地脚本最高速每秒80次. 看来之前优化mysql 批量插入仅是影响脚本效率的冰山一角. 在用户登陆高峰期单一落地脚本每秒仅能执行2-3次. 显然http接口的服务不光是提供给一项业务. 高峰期应该有其他服务在争抢服务... 无语了, 为今之计智能放弃http接口. 脚本自身调用http的响应数据并做缓存.

最后修改:2019 年 11 月 04 日 11 : 41 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论