分类目录归档:服务器

Vagrant CentOS 共享目录挂载问题解决

一直以来都是使用Vagrant与VirtualBox运行CentOS系统来搭建环境,然而有一天突然出现Windows下面的目录无法映射进去了,报错:

D:\project\vagrant\centos64php56>vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
  default: Adapter 1: nat
  default: Adapter 2: hostonly
==> default: Forwarding ports...
  default: 80 (guest) => 8080 (host) (adapter 1)
  default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
  default: SSH address: 127.0.0.1:2222
  default: SSH username: vagrant
  default: SSH auth method: private key
  default: Warning: Connection reset. Retrying...
  default: Warning: Connection aborted. Retrying...
  default: Warning: Connection reset. Retrying...
  default: Warning: Remote connection disconnect. Retrying...
==> default: Machine booted and ready!
[default] GuestAdditions versions on your host (5.1.26) and guest (4.3.6) do not matc
Loaded plugins: fastestmirror
Setting up Install Process
Loading mirror speeds from cached hostfile
 * base: mirrors.btte.net
 * epel: repo.fedoralinux.ir
 * extras: mirrors.btte.net
 * updates: mirrors.btte.net
No package kernel-devel-2.6.32-358.23.2.el6.x86_64 available.
Package gcc-4.4.7-18.el6.x86_64 already installed and latest version
Package binutils-2.20.51.0.2-5.47.el6_9.1.x86_64 already installed and latest version
Package 1:make-3.81-23.el6.x86_64 already installed and latest version
Package 4:perl-5.10.1-144.el6.x86_64 already installed and latest version
Package bzip2-1.0.5-7.el6_0.x86_64 already installed and latest version
Nothing to do
Copy iso file C:\Program Files\Oracle\VirtualBox\VBoxGuestAdditions.iso into the box
Installing Virtualbox Guest Additions 5.1.26 - guest version is 4.3.6
Verifying archive integrity... All good.
Uncompressing VirtualBox 5.1.26 Guest Additions for Linux...........
VirtualBox Guest Additions installer
Removing installed version 5.1.26 of VirtualBox Guest Additions...
vboxadd.sh: Stopping VirtualBox Additions.
Copying additional installer modules ...
Installing additional modules ...
vboxadd.sh: Starting the VirtualBox Guest Additions.
Failed to set up service vboxadd, please check the log file
/var/log/VBoxGuestAdditions.log for details.
An error occurred during installation of VirtualBox Guest Additions 5.1.26. Some func
In most cases it is OK that the "Window System drivers" installation failed.
vboxadd.sh: Starting the VirtualBox Guest Additions.
vboxadd.sh: failed: Look at /var/log/vboxadd-install.log to find out what went wrong.
vboxadd.sh: failed: modprobe vboxguest failed.
==> default: Checking for guest additions in VM...
  default: The guest additions on this VM do not match the installed version of
  default: VirtualBox! In most cases this is fine, but in rare cases it can
  default: prevent things such as shared folders from working properly. If you see
  default: shared folder errors, please make sure the guest additions within the
  default: virtual machine match the version of VirtualBox you have installed on
  default: your host and reload your VM.
  default:
  default: Guest Additions Version: 4.3.6
  default: VirtualBox Version: 5.1
==> default: Configuring and enabling network interfaces...
  default: SSH address: 127.0.0.1:2222
  default: SSH username: vagrant
  default: SSH auth method: private key
==> default: Mounting shared folders...
  default: /home/rc => D:/project/vagrant/centos64php56
Vagrant was unable to mount VirtualBox shared folders. This is usually
because the filesystem "vboxsf" is not available. This filesystem is
made available via the VirtualBox Guest Additions and kernel module.
Please verify that these guest additions are properly installed in the
guest. This is not a bug in Vagrant and is usually caused by a faulty
Vagrant box. For context, the command attempted was:

mount -t vboxsf -o uid=500,gid=500 home_rc_ /home/rc

The error output from the command was:

/sbin/mount.vboxsf: mounting failed with the error: No such device

这里提示虚拟机里CentOS的VBoxGuestAdditions与VirtualBox的版本匹配,需要升级:

[default] GuestAdditions versions on your host (5.1.26) and guest (4.3.6) do not matc

应该是升级了VirtualBox导致的。然而自动安装新插件失败:

vboxadd.sh: Starting the VirtualBox Guest Additions.
Failed to set up service vboxadd, please check the log file
/var/log/VBoxGuestAdditions.log for details.
An error occurred during installation of VirtualBox Guest Additions 5.1.26. Some func
In most cases it is OK that the "Window System drivers" installation failed.
vboxadd.sh: Starting the VirtualBox Guest Additions.
vboxadd.sh: failed: Look at /var/log/vboxadd-install.log to find out what went wrong.
vboxadd.sh: failed: modprobe vboxguest failed.

导致共享目录无法映射。但是虚拟机仍然是启动成功的,可以ssh进去,或者使用sftp挂载。
Google了下有说是VirtualBox bug的,也有说是Windows问题的,各种折腾不能解决。升级插件也无效:vagrant plugin install vagrant-vbguest。重新安装VirtualBox和Vagrant,并不会影响现有虚拟机及网络配置,但不能解决问题。
直到看到这篇文章,决定从CentOS入手解决。
启动虚拟机后,使用sftp上传VBoxGuestAdditions.iso,ssh进入手动安装:

$ sudo
$ mount VBoxGuestAdditions.iso -o loop /mnt
$ cd /mnt
$ sh VBoxLinuxAdditions.run

安装失败,查看日志:

Building the main Guest Additions module                   [FAILED]
(Look at /var/log/vboxadd-install.log to find out what went wrong)

vboxadd-install.log日志:

/tmp/vbox.0/Makefile.include.header:97: *** Error: unable to find the sources of your current Linux kernel. Specify KERN_DIR=<directory> and run Make again.  Stop.

参照这篇文章,查看kernel版本

$ rpm -qa kernel\* | sort
kernel-2.6.32-358.23.2.el6.x86_64
kernel-devel-2.6.32-696.10.2.el6.x86_64
kernel-firmware-2.6.32-358.10.2.el6.noarch
kernel-headers-2.6.32-696.10.2.el6.x86_64

$ uname -r
2.6.32-358.10.2.el6.x86_64

其实一开始是更多版本不匹配的,尝试更新kernel:

yum update
yum install kernel-headers kernel-devel

结果部分更新失败:

Warning: No matches found for: kernel-devel
No Matches found

参照这里解除版本锁定,设置enabled = 0:

$ vim /etc/yum/pluginconf.d/versionlock.conf

再次运行升级kernel就可以了。安装成功后,重启后,再次运行sh VBoxLinuxAdditions.run 就可以了。事实上Vagrant启动时就会自动安装VBoxGuestAdditions:

[default] GuestAdditions 5.1.26 running --- OK.

Vagrant升级到后发现vagrant up初始化下载box卡住了,那是你的vagrant版本太高与对应的powershell版本对应不上,可以下载最新的powershell安装即可。
如果box下载很慢,可以参照这里的方法自我映射取得url单独下载:

https://app.vagrantup.com/box-cutter/boxes/centos73
==> https://atlas.hashicorp.com/box-cutter/boxes/centos73/versions/2.0.21/providers/virtualbox.box

参考链接:
Guest Additions Version error on VirtualBox5
VirtualBox下挂载共享文件目录问题处理
Resolving GuestAdditions version mismatch in vagrant/homestead vm (failed to mount shared folders / modprobe vboxsf failed)
Problem installing virtualBox guest additions
Warning: No matches found for: kernel-devel
Vagrant up hangs forever on Windows 7, Vagrant 1.9.7, VirtualBox 5.1.22.r11512

TCP UDP探索

最近开发新接口,为实现更高的性能,深入考察了下TCP,UDP通信协议。之前的开发当中都是基于HTTP的接口开发,简单易用,各个编程语言都支持;web服务器、中间件成熟,方便扩展;支持安全加密等等。与自定义Socket通信相比,HTTP仍然不够高效。

TCP协议,也叫传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。每建立一个TCP连接需要三次握手,关闭时又需要四次握手;传输过程当中,每发送一个包,都要接收一个确认回复,如果在一定时间内未收到,则重发,如果是同一块数据分包传输,则回复所需要的包序号;在发送和接收时都要计算校验和。由于有严格的数据包传输顺序和校验,因为接收到的数据将会是顺序。

UDP协议,也叫用户数据报协议(User Datagram Protocol)是一个简单的面向数据报的传输层协议。它只管数据包发送,并不没有TCP的连接控制、回复确认,并不知道对方是否接收成功。因为并不等待接收方回复,发送端可以连续发送数据,接收端接收到数据并不一定是顺序的。因此相比TCP,UDP的有更高的传输效率,更低的可靠性。如果需要可靠性,则需要发送确认包,类似TCP那样子。

通常HTTP协议基于TCP实现,保证收到的内容是完整、顺序的。在HTTP 1.0时,每一次请求都需要建立TCP连接,关闭TCP连接;在HTTP 1.1时,同一TCP连接则可以在活跃(Keep-Live)间期内复用。HTTP是无连接的,每次处理完成请求即断开连接,一方面可以节省服务器资源,另一方面却浪费连接时间。如果需要维持客户端的Keep-Alive状态,则又需要消耗更多服务器资源,需要权衡。

WebSocket是基于TCP 连接上进行全双工通讯的协议,允许服务端直接向浏览器发送消息,不再需要ajax轮询。它也是在TCP上面的实现,使用类似HTTP请求的握手协议,进行TCP通信切换。WebRTC,也叫Web即时通信(Web Real-Time Communication),则是一个支持浏览器之间进行实时语音对话或视频对话的API。它是建立于TCP/UDP之上的点对点通信,客户端基于上层RTCDataChannel进行通信而非底层TCP/UDP。

Google推出了基于UDP的QUIC协议(快速UDP网络连接)实现的HTTP传输,可以快速在两个端点间创建连接,支持多路复用连接。对于丢包问题,则采用冗余传输,能够根据接收到的包,对缺失的包进行恢复(类似RAID)。对于一个TCP连接,需要四个参数:源IP/端口,目的IP/端口,任何一个改变都需要重新建立连接。QUIC则不需要这些,因为UDP并不需要来源信;它使用UUID来标识一个连接,只要这个UUID在,那么这个连接会话就能够继续(与HTTP会话有点像)。因此在不同网络,特别是移动网络之间的切换,都将保持稳定(参考:Mosh)。目前Google的网站已经支持,并可以在Chrome浏览器的里面查看连接情况:chrome://net-internals/#quic。

通常说TCP是可靠连接,可用于数据流,复杂网络情况;UDP由于缺少确认机制等,属不可靠连接,通常应用于网络情况良好(内部)或者允许丢包的场景。事实上UDP也应用于游戏,VoIP等需要高效传输的场景。当网络状况糟糕时,使用TCP通信反而可能更差,因为需要更多步骤建立连接和确认。参考这里:QQ 为什么采用 UDP 协议,而不采用 TCP 协议实现?。TCP连接是有状态的,服务端需要去维持,及TIME_WAIT问题。如果连接一段时间没用,TCP并不能感知连接是否仍然有效,需要自定义心跳包,维持连接。UDP则是无连接状态的,只要知道目标IP及端口即可以发送。

UDP通信简单许多,不需要socket_connect和socket_connect,主动暴露一端(服务端)需要socket_bind。例如,服务端:

<?php
$socket = stream_socket_server("udp://127.0.0.1:1113", $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
    die("$errstr ($errno)");
}

do {
    $pkt = stream_socket_recvfrom($socket, 1024, 0, $peer);
    echo "$peer,$pkt\n";
    //客户端互相通信时,注释下面一行
    stream_socket_sendto($socket, date("D M j H:i:s Y\r\n"), 0, $peer);
} while ($pkt !== false);

客户端:

<?php
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

$msg = "Ping !";
$len = strlen($msg);

socket_sendto($sock, $msg, $len, 0, '127.0.0.1', 1113);

//sleep(30)
$res = socket_recv( $sock , $reply , 2045 , MSG_WAITALL);
echo $reply;

socket_close($sock);

服务端的代码里面有个stream_socket_sendto(或者socket_sendto),向客户端发送消息。UDP并不需要向客户端回复消息,这并像TCP在同一个连接里面回复,而是另一次UDP通信罢了。实际上,两个客户端之间就能够互相通信(分别使用不同端口),任何能够拿到对方IP和端口,都能够向其发送消息。复制客户端代码,更改目标IP和端口为服务端打印出来的信息,然后运行,原先的客户端也将收到消息。利用中间服务器交换两个客户端的IP和端口,以便直接通信,是NAT穿透常用的方法。

综上,使用TCP可以保证数据可靠传输,复用连接,比如请求-应答模型;使用UDP则可以更高效发送数据,但能容忍部分丢包的场景,比如视频画面;或者只发送不需响应的数据,比如日志;或者无需连接状态的场景(类似HTTP会话),减少服务器压力和客户端等待。当然,也可以在UDP基础上自定义传输规则,实现譬如Mosh或则TCP那样的应用。选择TCP或者UDP,应该综合考虑使用场景,网络,数据包大小,效率,安全等,而不是盲从字面上的“面向可靠的”或者“不可靠的”或者“高效的”。

参考链接:
如何理解HTTP协议的 “无连接,无状态” 特点?
Google’s QUIC protocol: moving the web from TCP to UDP(Google QUIC协议:从TCP到UDP的Web平台
如何看待谷歌 Google 打算用 QUIC 协议替代 TCP/UDP? – 回答作者: Trotyl Yu
How Mosh works
使用TCP协议的NAT穿透技术
PHP Socket通信
日志系统设计
可靠 UDP 传输
DDOS(拒绝服务攻击)
php使用socket感悟–tcp和udp
UDP socket programming in php
Does WebRTC use TCP or UDP?

日志系统设计

日志系统是项目开发/运维当中非常重要的一部分,提供产品使用情况,跟踪调试信息等。以前都是各个项目各自实现日志记录,比如PHP、JAVA各自实现一套,如果要跨项目/服务器进行查询/跟踪/统计,则比较麻烦,比如Web后台–>业务模块–>基础组件,客户端–>公共接口–>业务模块等等。
目前的项目都是将日志写入本地,首先需要定义统一的规范:

格式
DateTime | ServerIP | ClientIP | PID | RequestID|  Type | Level | Message|  Code

解释
DateTime:记录日志的当前时间戳
ServerIP:当前服务器IP
ClientIP:客户端IP
PID:进程ID
RequestID:交易号,即请求的唯一标识,使用uniqid(+UserCode)或UUID或,一次请求会有多条日志,,用于关联本次请求内的相关日志
Type:日志类型,比如统计,操作(审计),业务等
Level:日志等级
Message:日志内容,当为值为数组或者对象时转为JSON
Type为RUNTIME时,表示运行日志,属性:自定义
Type为HTTP时,表示来源请求,属性:Url,Method(Get/Post),Params,RemoteIP,UserAgent,ReferUrl[,Action,Method]
Type为REST时,表示外部调用,属性:Type(Http/Https),Url,Port,RequestParams,Response,RunTime;
Type为SQL时,表示SQL执行,属性:Sql,RunTime
Code:标识码,记录错误码、响应码、统计码、版本、自定义信息,方便统计

这里的RequestID由入口处自动产生,用于标识一次请求,关联所产生的所有日志,需要在各个项目之间传递。为了减少日志,Level通常为Info级别,避免产生过多日志。为了方便调试追踪,RequestID和Level也可以在其他参数中指明,比如HTTP头里面附加。
然后是日志收集:客户端收走日志系统,发送给日志系统服务端。
然后分析处理呈现:服务端将接收到的日志,发给处理其他组件分析处理,提供Web界面的查询系统。研发人员,可以错误信息,定位问题;获悉程序运行情况进行调优;大数据分析日志,得出产品使用情况;运维平台则可以进行业务报警。

日志产生由个语言依照规范自行实现,收集、保持则由FlumeKafka实现。Flume是一个分布式的日志收集、合并、移动系统,能够监控文件变化,将变化部分传输出去。Kafka是一个分布式的发布/订阅的消息流平台,包括Broker,Consumer,Producer都支持分布式,依赖Zookeeper实现。

在PHP上面的实现,一开始使用log4php,看起来很美好,但是性能很差,对业务影响较大。决定再次简化,砍掉不必要的东西(Socket,邮件等等),在C语言开发的PHP日志扩展SeasLog基础上在做开发,将日志文件保存在本地。为了减少日志所占用内存,每超过一定的大小的日志即进行保存,否则在最后进行保存,利用了Nginx的fastcgi_finish_request特性。生产上发现,每天产生的文件日志太大了,需要控制日志信息大小、等级,并及时清理。
对于Web后台,还结合FirePHP,将日志直接输出到浏览器,方便边运行变调试。

参考链接:
最佳日志实践
Optimal Logging
Flume+Kafka收集Docker容器内分布式日志应用实践
基于Flume的美团日志收集系统(一)架构和设计
Twitter是如何构建高性能分布式日志的
开源日志系统比较
Kafka剖析(一):Kafka背景及架构介绍
基于Flume的野狗实时日志系统的演进和优化
EVODelavega/phpkafka
有赞统一日志平台初探
RabbitMQ和kafka从几个角度简单的对比
Flume-ng的原理和使用
利用flume+kafka+storm+mysql构建大数据实时系统

HTTP文件分块上传下载

在开发中经常需要上传下载文件,涉及web页面,手机应用,线下服务器等,文件传输方式有HTTP,FTP,BT。。于是基于HTTP开发了统一文件上传下载接口。
由于上传涉及多端,网络状况复杂,需要能够支持断点续传,因此需要对文件进行分块和校验。
请求上传:

  • chunk:当前块编号
  • chunks:文件分块总数
  • md5:整个文件md5
  • content:文件块内容
  • size:文件块大小

服务端返回:

  • statu:状态,0失败,非0成功
  • chunk:需要上传的块编号
  • message:操作信息

服务端有几种情况

  • 1. 上传成功,md5已经存在,说明文件已存在,则返回上传成功,并结束上传,类似秒传
  • 2. 上传成功,md5已经不存在,但当前文件块已经存在,说明该文件已经上传过一部分,则返回成功,并给出需要开始上传的块号,类似断点续传
  • 3. 上传成功,文件块不存在,当前块号小于总块数减一,则返回上传成功,并给出下一块块号
  • 4. 上传成功,文件块不存在,当前块号等于总块数减一,进行合并,校验成功,则返回上传成功,并结束上传
  • 5. 上传成功,文件块不存在,当前块号等于总块数减一,进行合并,校验成功,则返回上传失败,从第一块重新开始上传
  • 6. 上传失败,服务端返回失败,则根据错误信息重传
  • 7. 上传失败,服务端没有返回,则重传当前块

客户端依据自定义的大小对文件进行切割,每次传递一个块,当服务端接收到当前块号为总块数减一则认为全部上传完毕,进行文件块合并,清除临时文件块,计算MD5,如果MD5与传递过来的相等则认为上传成功,否则失败,要求客户端从第一块重传。
这个是基于客户端顺序上传的,假如是并发上传呢?那么就需要在每个分块上传结束后触发合并,需要借助锁来管理。
又拍云表单分块上传则分为3步骤:

  • 1.初始化,上报文件信息
  • 2.上传分块
  • 3.上传结束,触发合并

百度的WebUploader则支持浏览器向服务端的断点续传,利用HTML5或Flash对文件进行分块,计算MD5。
HTTP分块下载,也就是断点续传下载,是根据HTTP1.1协议(RFC2616)中定义的HTTP头 Range和Content-Range字段来控制的:

  • 1. 客户端在HTTP请求头里面指明Range,即开始下载位置
  • 2. 服务端在HTTP响应头里面返回Content-Range,告知下载其实点和范围

服务端可以将文件MD5等加入Etag或自定义Header字段里面,HTTP客户端便可以分开请求数据,最后合并;否则永远都是从头开始。其实也可以把大文件切成多个小文件,再一个个下载回来合并。
有些web服务器直接支持文件上传和下载的断点续传,比如Nginx。

刚才的文件分块传输过程,有点像TCP通信,也有建立连接,分片,校验,重发,断开连接。
而这个将大任务分解为多个小任务进行处理的思想,即准备–>循环(并行)处理–>结束,可以应用到很多项目里面,比如大数据,爬虫。为Web管理系统写了jQuery插件用来处理的耗时任务,将一个耗时任务分解为多个循环请求,避免超时

  • 1. prepare:服务端返任务标示ID,总步骤,及附加参数
  • 2. process:根据返回参数进行循环请求
  • 3. complete:循环完成,触发结束

后面又延伸出另外一个插件,支持任意顺序步骤的处理。

参考链接:
聊聊大文件上传
md5 javascript
HTTP协议--断点续传
http断点续传的秘密
nginx-upload-module模块实现文件断点续传
通过curl测试服务器是否支持断点续传

分布式系统

好久以前写的了。在InfoQ上学习了分布式监控系统的设计与实现微信朋友圈技术之道,在架构设计的基础思想并不复杂。
首先是微信朋友圈技术之道,介绍了微信朋友圈团队的开发,团队仅4个人,因为他们站在巨人的肩膀上:
基础环境
全部才用C++开发
混合部署普通服务器
海量带宽

强大的基础设施
腾讯CDN,图片和视频上传、存储、分发
RPC框架
Key-Value(KV)存储系统
强大、方便、灵活的部署系统
强大的RPC框架
C++的框架
支持protobuf描述接口
支持进程/线程/协程多种模式:支持数十万的并发协程,方便编写和调试“同步”的网络调用和服务
从CGI到叶子服务器,全系统透明支持过载保护和QOS
为每次CGI调用自动生成全系统调用关系图
每个服务自动内建500多个监控项
高性能Key-value存储系统
三机一组,三机间支持数据一致性,容忍一台机器出错、自动切换
三机分布在一个数据中心的三个独立园区,在任意一个园区提供本区读写服务,容忍一个园区网络隔离

微信架构
接入层,维持长连接,避免重复连接开销;推送消息
逻辑层,注册/登录,消息,朋友圈,LBS;批处理,群聊,通知,好友推荐
存储代理层,账号,消息,关系链,朋友圈,群管理
存储层,Key-value存储
性能水平扩展(sharding)
相册,按照用户做水平扩展
发表,按照发表key做水平扩展
评论,按照评论key做水平扩展
时间线,按照用户做水平扩展

发表流程
上传图片到CDN,查找最近节点
调用朋友圈CGI
添加新发表
在相册增加新发表索引
添加时间线更新任务,批处理,给好友时间线添加新索引
返回发表成功

浏览流程
调用朋友圈CGI
拉取时间线
拉取新发表元数据
拉取CDN上的图片

数据单副本、索引写扩散、检查更新单读取
数据(发表)单副本:减少内存开销
索引写扩散、检查更新单读取:减少检查更新时的读扩散
写比较慢,失败可以重试;读不可等待

赞、评论与浏览
往赞、评论表插入发表索引和对应赞、评论

微信部署、接入与容灾
1地3园区对等部署,对等接入(电信、移动、联通);数据对等分布于同步;对等服务
容灾,任何两个园区都可以提供全量无损服务
数据中心分布:上海、深圳、香港、加拿大
通信优先专线服务;其次公网(加密)
各数据中心可独立提供服务
各数据中心通过idc queue异步同步写入(自动重试)

核心数据读写分析
相册与发表
本地idc写入,单向同步到其他idc
key的全局唯一性保证无冲突(根据idc预先生成)
时间线
本地idc只保存本地用户时间线的key,无需同步

跨洋同步的挑战和应对
挑战:大带宽延迟、丢包、乱序、低可靠性(专心中断)
应对:延迟、丢包、乱序达到一定程度自动从tcp切换到udp;专线中断自动切换到公网(AES加密)

评论、赞写冲突
因果一致性(不同数据中心保证key不重复,比如不同idc取模)

参考链接:
分布式监控系统的设计与实现
微信朋友圈技术之道

MySql 慢日志分析

最近老是碰上MySql报错:1203:User already has more than ‘max_user_connections’ active,之前都没出现过,感觉应该是慢查询导致的。向运维拷贝慢日志分析,慢日志开、启配置参考这里
拷贝出来的日志很大,需要按故障时间点做一下切割,以便缩小排查范围。按照这里提供的cutlogbytime.pl脚本运行却报错

[vagrant@centos64 mysql-log-filter-1.9]$ ./cutlogbytime.pl slow.log 1443103200 1443117600 > yestoday.log
: command not foundline 1:
: command not foundline 4:
./cutlogbytime.pl: line 5: use: command not found
: command not foundline 5:
./cutlogbytime.pl: line 6: use: command not found
: command not foundline 6:
: command not foundline 7:
'/cutlogbytime.pl: line 8: syntax error near unexpected token `{
'/cutlogbytime.pl: line 8: `if (@ARGV<2){

去掉顶行的空格后再运行,还是报错

[vagrant@centos64 mysql-log-filter-1.9]$ ./cutlogbytime.pl slow.log 1443103200 1443117600 > today.log
-bash: ./cutlogbytime.pl: /usr/bin/perl^M: bad interpreter: No such file or directory

最后参考stackoverflow上面的答案更改运行方式为Perl(而不是shell),就可以了。

[vagrant@centos64 mysql-log-filter-1.9]$ perl cutlogbytime.pl slow.log 1443103200 1443117600 > today.log

利用mysqlslowdump(perl脚本)来分析日志,-s参数表示排序方式:r表示影响行数(Rows),t表示耗时(Time),c表示查询次数(Count)

[vagrant@entos64 mysql-log-filter-1.9]$  perl mysqldumpslow.pl -s r -t 10 today4.log

Reading mysql slow query log from today4.log
Count: 1  Time=190.48s (190s)  Lock=0.00s (0s)  Rows=21829854.0 (21829854), xx[xxxx]@[192.168.10.139]
  SELECT /*!N SQL_NO_CACHE */ * FROM `errormessage`

Count: 32791  Time=40.95s (1342865s)  Lock=0.05s (1512s)  Rows=1.0 (32791), xx[xxxx]@10hosts
  select  *  from connectinfo where  ID=N  and AppType=N  ORDER BY CreateDatetime DESC LIMIT N

Count: 3  Time=3.71s (11s)  Lock=0.02s (0s)  Rows=300.0 (900), xx[xxxx]@2hosts
  select SeverName from errormessage where  ID='S'  and ServerType=N  and level=N  and MsgType <= N

第一个语句返回行数21829854,查看具体慢日志,之后需要插入这张表的进程均处于等待状态。

# Time: 150924  1:03:12
# User@Host: xx[xxxx] @  [192.168.10.139]  Id: 1493761
# Query_time: 190.479062  Lock_time: 0.000000 Rows_sent: 21829854  Rows_examined: 21829854
SET timestamp=1443027792;
SELECT /*!40001 SQL_NO_CACHE */ * FROM `errormessage`;
# Time: 150924  1:03:14
# User@Host: xx[xxxx] @  [192.168.10.168]  Id: 1498010
# Query_time: 59.669817  Lock_time: 57.159403 Rows_sent: 0  Rows_examined: 0
SET timestamp=1443027794;
insert into errormessage (`ID`,`ServerType`,`MsgType`,`Level`,`dev`,`content`,`EventTime`,`SeverName`) values ( '1217', '3', '4', '4', '827', 'erc:start erc error,songid=46243,keymd5=ee1275b26762e85a7f00e9890bdc092e,ercmd5=abbc3ea9102dbd003b7aa0547dcbf6fa', '2015-09-23 21:49:27', '192.168.15.117');
# User@Host: xx[xxxx] @  [192.168.10.205]  Id: 1494756
# Query_time: 157.211158  Lock_time: 154.673647 Rows_sent: 0  Rows_examined: 0
SET timestamp=1443027794;
insert into errormessage (`ID`,`ServerType`,`MsgType`,`Level`,`dev`,`content`,`EventTime`,`SeverName`) values ( '865', '3', '1', '2', '106', '检测正常!', '2015-09-24 01:01:18', '192.168.0.33');
# User@Host: xx[xxxx] @  [192.168.10.213]  Id: 1496479
# Query_time: 100.733230  Lock_time: 98.210902 Rows_sent: 0  Rows_examined: 0
SET timestamp=1443027794;
insert into errormessage (`ID`,`ServerType`,`MsgType`,`Level`,`dev`,`content`,`EventTime`,`SeverName`) values ( '2472', '3', '2', '4', '809', 'videoseripnoconfig', '2015-09-24 01:02:26', '192.168.0.18');

分析这几天的日志,发现故障时间点附近都是这个语句引起后面的SQL堵塞。原来是每天早上1点开始备份并同步全表数据,锁住了这个表导致后面的所有这个表的insert操作处于等待状态。mysqldump应该使用–single-transaction来避免锁表,类似下面这个

mysqldump –uuser -p --skip-opt -q -R  --single-transaction --default-character-set=utf8 --master-data=2  --create-option --no-autocommit –S ${sock} -B ${DBName}  > backup.sql

但是这样仍然是全表扫描进行备份,如果能够增量备份的话,影响要小很多,或者数据做冷热/新旧之分,定期将新(每天)/热数据转入旧(历史)/冷数据中。后面运维的解决方案是:升级数据库机器从虚拟机变为实体机,配置从机,并从从机进行备份同步。

上面mysqlslowdump使用影响行数来排序,事实上用另外两个类型(时间,次数)分析结果是connectinfo比较频繁,一直以来都认为是这个表的操作引起的。这里还尝试了其他工具来分析,使用mysqlsla.pl进行分析,相关参数选项参考这里

[vagrant@centos64 mysql-log-filter-1.9]$  perl mysqlsla.pl today.log
Auto-detected logs as slow logs
Report for slow logs: today4.log
60.57k queries total, 17 unique
Sorted by 't_sum'
Grand Totals: Time 5.38M s, Lock 3.22M s, Rows sent 21.86M, Rows Examined 184.46M


______________________________________________________________________ 001 ___
Count         : 25.59k  (42.24%)
Time          : 3905525.574451 s total, 152.643069 s avg, 113.07488 s to 2720.338946 s max  (72.64%)
  95% of Time : 3260112.482495 s total, 134.12789 s avg, 113.07488 s to 282.366041 s max
Lock Time (s) : 3168076.975558 s total, 123.820721 s avg, 108.548105 s to 311.639359 s max  (98.45%)
  95% of Lock : 2961933.212121 s total, 121.860167 s avg, 108.548105 s to 123.487106 s max
Rows sent     : 0 avg, 0 to 0 max  (0.00%)
Rows examined : 54 avg, 0 to 4.92k max  (0.75%)
Database      :
Users         :
        xx@ 192.168.10.147 : 10.65% (2724) of query, 10.26% (6215) of all users
        xx@ 192.168.10.209 : 10.33% (2643) of query, 10.16% (6156) of all users
        xx@ 192.168.10.205 : 10.16% (2599) of query, 9.97% (6036) of all users
        xx@ 192.168.10.211 : 10.13% (2591) of query, 9.98% (6042) of all users
        xx@ 192.168.10.207 : 9.93% (2541) of query, 9.95% (6024) of all users
        xx@ 192.168.10.161 : 9.83% (2515) of query, 9.84% (5960) of all users
        xx@ 192.168.10.149 : 9.81% (2510) of query, 9.95% (6028) of all users
        xx@ 192.168.10.215 : 9.76% (2498) of query, 9.85% (5963) of all users
        xx@ 192.168.10.168 : 9.71% (2485) of query, 9.69% (5868) of all users
        xx@ 192.168.10.213 : 9.69% (2480) of query, 9.66% (5851) of all users

Query abstract:
SET timestamp=N; UPDATE connectinfo SET devicetag='S', connectipaddress='S', updatedatetime=now() WHERE ID=N AND apptype=N;

Query sample:
SET timestamp=1443027797;
update connectinfo set DeviceTag='1070A416AF000000', ConnectIPAddress='60.174.116.165', UpdateDatetime=now() where ID=5358 and AppType=0;

______________________________________________________________________ 002 ___
Count         : 32.79k  (54.14%)
Time          : 1344378.871914 s total, 40.99841 s avg, 2.000747 s to 1944.548192 s max  (25.01%)
  95% of Time : 587407.556704 s total, 18.85678 s avg, 2.000747 s to 233.465042 s max
Lock Time (s) : 1512.917798 s total, 46.138 ms avg, 76 ▒s to 114.302 ms max  (0.05%)
  95% of Lock : 1414.978902 s total, 45.423 ms avg, 76 ▒s to 50.514 ms max
Rows sent     : 1 avg, 1 to 1 max  (0.15%)
Rows examined : 4.92k avg, 4.92k to 4.92k max  (87.41%)
Database      :
Users         :
        xx@ 192.168.10.209 : 10.24% (3359) of query, 10.16% (6156) of all users
        xx@ 192.168.10.149 : 10.16% (3331) of query, 9.95% (6028) of all users
        xx@ 192.168.10.147 : 10.11% (3315) of query, 10.26% (6215) of all users
        xx@ 192.168.10.211 : 10.03% (3288) of query, 9.98% (6042) of all users
        xx@ 192.168.10.207 : 10.02% (3285) of query, 9.95% (6024) of all users
        xx@ 192.168.10.161 : 9.97% (3268) of query, 9.84% (5960) of all users
        xx@ 192.168.10.215 : 9.96% (3266) of query, 9.85% (5963) of all users
        xx@ 192.168.10.205 : 9.92% (3254) of query, 9.97% (6036) of all users
        xx@ 192.168.10.168 : 9.86% (3234) of query, 9.69% (5868) of all users
        xx@ 192.168.10.213 : 9.73% (3191) of query, 9.66% (5851) of all users

Query abstract:
SET timestamp=N; SELECT * FROM connectinfo WHERE ID=N AND apptype=N ORDER BY createdatetime DESC LIMIT N;

Query sample:
SET timestamp=1443027795;
select  *  from connectinfo where  ID=7646  and AppType=0  ORDER BY CreateDatetime DESC LIMIT 1;

______________________________________________________________________ 003 ___
Count         : 842  (1.39%)
Time          : 66663.314786 s total, 79.172583 s avg, 2.011408 s to 673.604537 s max  (1.24%)
  95% of Time : 56684.989954 s total, 70.944919 s avg, 2.011408 s to 193.623235 s max
Lock Time (s) : 48221.988255 s total, 57.27077 s avg, 69 ▒s to 185.402303 s max  (1.50%)
  95% of Lock : 40627.196184 s total, 50.847555 s avg, 69 ▒s to 166.67704 s max
Rows sent     : 0 avg, 0 to 0 max  (0.00%)
Rows examined : 0 avg, 0 to 0 max  (0.00%)
Database      :
Users         :
        xx@ 192.168.10.207 : 11.64% (98) of query, 9.95% (6024) of all users
        xx@ 192.168.10.205 : 11.28% (95) of query, 9.97% (6036) of all users
        xx@ 192.168.10.213 : 10.93% (92) of query, 9.66% (5851) of all users
        xx@ 192.168.10.161 : 10.45% (88) of query, 9.84% (5960) of all users
        xx@ 192.168.10.149 : 10.33% (87) of query, 9.95% (6028) of all users
        xx@ 192.168.10.211 : 9.74% (82) of query, 9.98% (6042) of all users
        xx@ 192.168.10.147 : 9.38% (79) of query, 10.26% (6215) of all users
        xx@ 192.168.10.215 : 9.38% (79) of query, 9.85% (5963) of all users
        xx@ 192.168.10.168 : 9.03% (76) of query, 9.69% (5868) of all users
        xx@ 192.168.10.209 : 7.84% (66) of query, 10.16% (6156) of all users

Query abstract:
SET timestamp=N; INSERT INTO errormessage (id,servertype,msgtype,level,dev,content,eventtime,severname) VALUES ( 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S')1;

使用mysqlsla可以看SQL语句的执行数量/比例,影响行数,用户,占比等。,从这里看很可能认为是connectinfo表(95%以上)引起,SHOW PROCESSLIST也是如此 。
由于这个文件我是单独下载回来的,运行mysqlsla.pl时候碰到很多错误,逐个安装解决了

[vagrant@centos64 mysql-log-filter-1.9]$  perl mysqlsla.pl today.log
Can't locate Time/HiRes.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at mysqlsla.pl line 2092.
BEGIN failed--compilation aborted at mysqlsla.pl line 2092.
#解决方法
[vagrant@centos64 mysql-log-filter-1.9]$ sudo yum install perl-Time-HiRes

[vagrant@centos64 mysql-log-filter-1.9]$ perl -MCPAN -e 'install DBI'
Can't locate CPAN.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .).
BEGIN failed--compilation aborted.

#解决方法
[vagrant@centos64 mysql-log-filter-1.9]$ sudo yum install perl-DBI

正确的方法是应该检测对应根目录下面的Makefile.PL,比如

[vagrant@centos64 percona-toolkit-2.2.15]$ perl Makefile.PL
#如果报以下错误,需要先安装对应模块,简单点就是
#sudo yum install perl-devel
Can't locate ExtUtils/MakeMaker.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at Makefile.PL line 1.
BEGIN failed--compilation aborted at Makefile.PL line 1.

然后安装对应模块,参考这里
使用Percona公司的工具pt-query-digest来分析也得到了同mysqlsla类似的结果

[vagrant@vagrant-centos64 bin]$ pt-query-digest ../../today4.log

# 9.8s user time, 700ms system time, 21.05M rss, 73.66M vsz
# Current date: Mon Oct  5 05:52:01 2015
# Hostname: vagrant-centos64.vagrantup.com
# Files: ../../today.log
# Overall: 60.57k total, 17 unique, 4.54 QPS, 402.68x concurrency ________
# Time range: 2015-09-23 22:17:29 to 2015-09-24 02:00:00
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time        5376198s      2s   2720s     89s    258s    118s     57s
# Lock time        3217840s       0    312s     53s    118s     60s    48ms
# Rows sent         20.85M       0  20.82M  361.00    0.99  84.46k    0.99
# Rows examine     175.91M       0  20.82M   2.97k   4.71k  84.48k   4.71k
# Query size         7.85M      64     597  135.90  151.03   27.56  112.70

# Profile
# Rank Query ID           Response time      Calls R/Call   V/M   Item
# ==== ================== ================== ===== ======== ===== ========
#    1 0xF1132168DB0BFC57 3905525.5745 72.6% 25586 152.6431 61.61 UPDATE connectinfo
#    2 0xD4B317E755A0ABD7 1344378.8719 25.0% 32791  40.9984 30... SELECT connectinfo
#    3 0xE23849EE6FB19DAE   66663.3148  1.2%   842  79.1726 62.99 INSERT errormessage
...

# Query 1: 7.52 QPS, 1.15kx concurrency, ID 0xF1132168DB0BFC57 at byte 16243195
# This item is included in the report because it matches --limit.
# Scores: V/M = 61.61
# Time range: 2015-09-24 01:03:17 to 02:00:00
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         42   25586
# Exec time     72 3905526s    113s   2720s    153s    271s     97s    124s
# Lock time     98 3168077s    109s    312s    124s    118s     14s    118s
# Rows sent      0       0       0       0       0       0       0       0
# Rows examine   0   1.33M       0   4.80k   54.39       0  504.65       0
# Query size    48   3.78M     149     157  154.94  151.03    0.52  151.03
# String:
# Hosts        192.168.10.147 (2724/10%)... 9 more
# Users        gate
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms
#  10ms
# 100ms
#    1s
#  10s+  ################################################################
# Tables
#    SHOW TABLE STATUS LIKE 'connectinfo'\G
#    SHOW CREATE TABLE `connectinfo`\G
update connectinfo set DeviceTag='10705BDDCD000000', ConnectIPAddress='115.231.63.78', UpdateDatetime=now() where ID=6912 and AppType=0\G
# Converted for EXPLAIN
# EXPLAIN /*!50100 PARTITIONS*/
select  DeviceTag='10705BDDCD000000', ConnectIPAddress='115.231.63.78', UpdateDatetime=now() from connectinfo where  ID=6912 and AppType=0\G

PS:这个问题解决后没几天,数据库又出问题,以为又是慢SQL什么的,结果是交换机网口问题。。。数据传输太慢,导致主从不同步。。。

参考链接:
mysql 慢日志分析
慢日志按时间截取
MySQL 之 slow log
MySQL优化—工欲善其事,必先利其器(2)
日志常用统计技巧
性能优化之MySQL优化(一)
mysqlsla的安装与使用
安装DBI组件。 Can’t locate DBI.pm in @INC-mysql接口
Issue 12: Can’t locate Time/HiRes.pm
打开MySQL的慢查询记录
“Can’t locate ExtUtils/MakeMaker.pm” while compile git
analysing slow MySQL queries with pt-query-digest
mysqldump备份原理
mysqldump –single-transaction, yet update queries are waiting for the backup
mysql 利用binlog增量备份,还原实例
MySQl备份恢复策略(完全+增量备份策略)

Git及GitHub 使用

在前面的文章里面,经常会看到使用Git来下载代码,比如

$ git clone https://github.com/zeromq/zeromq4-x.git

Git 是有Linux开发者Linus Torvalds开发的分布式版本控制软件,最初用于Linux内核代码管理,它不需要服务器端软件,就可以进行版本控制,使得离线/本地开发非常方便。与SVN不同,Git基本在本机工作(就像在本机也搭建了一个SVN服务器),具有以下优点:

  • 可以向本地提交代码,当服务器不可用时,仍然可以离线提交代码
  • 可以向本地提交代码,在本地功能开发完成后,再向服务器提交有用的变更,项目代码不会有频繁变更
  • 分布式存储,每个开发者均有一份代码,不用担心服务器上的代码丢失
  • 支持Pull Request,功能完成后通知其他成员进行代码审查

Git提倡使用主干作为稳定可发布的代码,使用分支进行开发,与我们的软件管理思路大致一致:分支开发,分支测试,分支/主干发布。多人协作项目,需要有良好的分支开发意识,以免在主干上互相冲突和影响软件稳定。
通过MINGW32安装Git,在Windows下面也可以使用Git。它提供了在右键菜单里面集成了Git管理的一些操作,也提供了Bash进行交互。Git常用操作包括:

# 获取帮助
$ git --help
# 获取某个git命令的帮助信息,使用默认浏览器查看
$ git help branch

# 初始化当前目录作为git仓库
$ git init
# 复制一个远程仓库到本地
$ git clone <url>

# 切换到主分支master(默认)
$ git checkout master

# 设置个人账号信息,提交代码时会同时提交这些信息
$ git config --global user.name "mystery"
$ git config --global user.email mystery@example.com:

# 提交全部代码变更
$ git add --all
# 仅提交boot.php
$ git add boot.php
# 仅提交路径generator/lib
$ git add generator/lib

# 仅提交路径generator/lib
$ git add generator/lib

# 删除文件 test.php
$ git rm test.php

# 移动文件 test.php
$ git mvtest.php

# 查看当前代码是否有未提交的变更
$ git status
# 比较代码差异
$ git diff
# 确认提交代码并备注
$ git commit -m 'test comment '

# 自动提交所有文件变更,不需要上面的步骤了
$ git commit -a -m 'added new comments'
# 查看提交历史记录
$ git log
# 查看历史记录前2条,并显示变更
$ git log -p -2

# 撤销上一次commit修改
$ git reset HEAD

# 与远程仓库库同步
$ git fetch origin
$ git rebase origin/master

# 多个本地合并commit合并成一个
$ git rebase -i origin/master

# 推送本地更新到远程仓库
$ git push --force origin feature-x

# 在master分支上创建分支develop,-b则表示创建分支并切换到该分支
$ git checkout -b develop master
# 在develop分支上创建feature-x分支
$ git checkout -b feature-x develop

# 列出所有分支
$ git branch
# 创建master分支
$ git branch master
# 删除分支feature-x
$ git branch -d feature-x

# 在mater上面合并feature-x分支代码
$ git checkout master
$ git merge feature-x

# 合并远程仓库代码到本地
$ git pull origin master
$ git fetch origin
$ git merge --no-ff origin/master

# 查看当前镜像
$ git tag
# 为当前仓库创建一个镜像标签
$ git tag -a v1.0 -m 'publish '

在已有项目目录上使用git

git init .
git remote add origin <repository-url>
git pull origin master
git add .
git commit -am "commit message"
git push origin master

Git还提供了一个工具,可以将svn仓库代码导入git仓库,参考这里

GitHub是一个著名的在线代码托管/分享/交流网站,提供免费的代码托管服务,支持分布式版本控制软件Git管理的工程代码,方便交流和协作。在这里可以找到各种各样的开源软件代码,比如PHP-SRC,对于程序员的帮助也是显而易见的,反之从一个程序员关注的项目也可以看出一个人的开发方向和水平。

GitHub开发了一个客户端用于与GitHub网站进行交互,隐藏了一些操作的细节,相比SVN要大气,方便一些。但是在线安装的方式对于国内并不方面。。
通过GitHub客户端可以直接创建、管理GitHub线上托管的代码。
git-1
git-2
与TortoiseSVN类似,也有TortoiseGit用于Windows下面的Git管理。
作公司内部的代码管理,可以使用GitLab来搭建私有的代码托管服务器,参考这里

参考链接:
Git 使用规范流程
Github使用指南
Pro Git(中文版)
使用 Git 管理源代码
Git工作流程
Git 分支管理
介绍一个成功的 Git 分支模型
从其他代码管理工具迁移到Git
Git分支管理策略
[Sever Hacks] 搭建私有 GitLab 代码托管服务器

MySQL秒杀优化

今天学习了楼方鑫先生《基于SQL的秒杀解决方案》,讲解了如何定位和优化秒杀业务中问题。
首先介绍了库存业务,库存可以分为前端库存,后端库存,实体库存。秒杀时,存在的主要问题

  • 库存数据不准确,下单、付款后,得知零库存;超卖或少卖
  • 废单较多,只下单不付款,转化率低
  • 热点商品,拖垮整个站

秒杀过程中,需要解决的技术点包括

  • 余额减一
  • 操作明细,方便追溯对账,防止一个帐号多次参与
  • 完整事务,保障记录明细与扣减库存同时完成
  • 数据落地,内存数据不可靠

针对库存技术要求,做了多个库存解决方案,比如Mysql + Read /Write Cache 。Read Cache方案不足是读有延迟影响用户体验;Write Cache方案存在多个APP写数据不一致性。Mysql + Cache + NoSQL方案则太复杂未实现。

于是又重新回到优化Mysql上。Mysql优势在于事务机制成熟,程序稳定。存在技术难点:单行并发,热点商品,瞬间压力,前一分钟,千万用户,容易堵塞,拖垮网站。于是从以下几个方面进行优化

  • 事务优化,单行更新
  • 并发优化,最大并发数
  • 排队优化,抢同一商品

分析秒杀时的处理逻辑,扫描系统代码,发现大部分程序都在等待确认Update记录数,才提交事务。

  • 开启事务
  • Insert库存明细
  • Update库存余额
  • 提交事务

在良好设计下,Mysql的Insert操作,不使用自增列是不会阻塞请求。但是Mysql的Update同一条记录是串行的,需要等远程客户端发送提交命令后才能释放锁,让其他会话继续。简单的更新操作,不考虑IO和锁冲突,一条语句执行的时间大约是0.1ms,一般条件下的网络时延为0.4-0.8ms,即等待事务提交通知的时间比真正SQL执行的时间长数倍。
于是扩展了SQL语法(OneSQL),指定在Update执行完后自动提交,不需要等待客户端发送提交命令,从而节约这一个网络来回的事务等待时间,提升网络性能。

秒杀时如果遇到大量请求需要进行排队,以免太多的请求拖垮Mysql

  • 在应用层排队的缺点,应用需要改造,使用统一框架(需要考虑跨语言),应用集群扩容时,控制不准确(连接数分配)
  • 在Mysql排队的优点,应用改造极少,只需修改少量SQL语句,无需统一框架,排队精确,发挥InnoDB性能。

于是开发了兼容Mysql的分布式数据访问层(OneProxy),为并发请求进行排队。

另外,还对热点商品进行独立数据库拆分和优化。目前,双十一前商品便已挂出,用户可以收藏或预购,对于商家而言可以准备更多商品;对于平台而言可以预先发现热点商品做优化。

总结,对于业务优化,需要循序渐进,深入了解业务逻辑和技术点,比较不同的解决方案,就算是平常的update操作也有优化空间;同时需要从其他方面进行特定优化,如高并发排队,热点数据分离等。

除了后端数据库优化,对于秒杀抽奖业务,问题的解决核心就是控制单位时间内的流量,使其不超过后端的处理能力。前端的做法包括

  • 分批次(少量多次)进行秒杀
  • 先玩游戏再抢购,如抽奖
  • 随机过滤掉部分请求,仅部分进入系统,如1/10
  • 阈值控制,一旦达到阈值,不再接收新请求
  • 预约排号,未排号用户返回失败(用户分类)
  • 验证码验证

另外,OneProxy 提供的连接池功能对于PHP非常有用。PHP运行在CGI下面,每一个请求到来便需要重新创建一个数据库连接与Mysql进行交互,并发量大量的情况下便会出现:too many connetion,乃至拖垮数据库:mysql server has gone away,影响其他业务。因此Mysql连接池,对于PHP显得非常重要。

更新:小米网在开发抢购系统的时候,最早使用PHP + Mysql碰到了一些问题,例如并发性能,数据一致性,在OneSQL上面都已经做了改进优化,只是小米自己使用Go语言重构,开发大秒系统(BigTap)。

参考链接:
限量秒杀等高并发活动的正确性如何保证?
MySQL 5.6.17/Percona5.6.16/MariaDB 10.0.11/OneSQL 5.6.16 TpmC测试
由12306.cn谈谈网站性能技术
“米粉节”背后的故事——小米网抢购系统开发实践
Web系统大规模并发——电商秒杀与抢购
OneProxy : 如何给PHP页面以及其他Ruby/Python/Go程序添加连接池功能?
基于Swoole实现的Mysql连接池

PHP性能分析之xhprof

xdebug讲到了使用xdebug对php程序进行性能分析,这里再介绍另外一个工具:xhprof,facebook出品。xhprof是一个函数级别的分层性能报告工具,包括调用次数,阻塞时间,CPU时间和内存使用情况。
首先,下载并安装xhprof扩展

tar -zxvf xhprof-0.9.4.tgz 
cd xhprof-0.9.4
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make
make install
#拷贝扩展
cp /usr/local/php/lib/php/extensions/no-debug-non-zts-20100525/xhprof.so /usr/local/php/lib/php/extensions/

mkdir -p /tmp/xhprof
chmod 755 /tmp/xhprof
chown www:www /tmp/xhprof

xhprof自带的界面工具比较简单,这里推荐使用另外一个UI:XHProf.io。下载解压到web目录下面,重命名/xhprof/includes/目录下的config.inc.sample.php为/xhprof/includes/config.inc.php,并更改文件:

return array(
	'url_base' => 'http://192.168.84.2:8502/', //本地的XHProf.io站点
	'url_static' => null, // When undefined, it defaults to $config['url_base'] . 'public/'. This should be absolute URL.
	'pdo' => new PDO('mysql:dbname=test;host=192.168.84.3;charset=utf8', 'root', 'root') //本地的数据库配置,只支持PDO
);

在test数据库上运行/xhprof/setup/database.sql,创建性能检测相关的表结构。
由于XHProf会去github上检测版本,国内访问较慢,建议不要检测。更改/xhprof/includes/bootstrap.inc.php如下:

	curl_setopt_array($ch, array(
		CURLOPT_URL => 'http://192.168.84.2:8502/version.json', //本地的XHProf.io站点下面
		CURLOPT_HEADER => FALSE,
		CURLOPT_RETURNTRANSFER => TRUE
	));

然后更改php.ini配置,在最后加上以下内容:

;xhprof
[xhprof]
extension=xhprof.so;
xhprof.output_dir=/tmp/xhprof

; Automatically add files before PHP document.
; XHProf.io站点下的prepend.php
auto_prepend_file = /usr/local/nginx/xhprof/inc/prepend.php

; Automatically add files after PHP document.
; XHProf.io站点下的append.php
auto_append_file = /usr/local/nginx/xhprof/inc/append.php

auto_prepend_file为每次php脚本运行前,自动加载并运行的文件;auto_append_file为每次php脚本运行后,自动加载并运行的文件。这样可以省去每次在需要检测的php文件里面写xhprof_enable/xhprof_disable等调用代码,不必更改原有代码(无侵入)。
注意:如果你访问了php页面却收集不到情况,可能是你的代码里面写了exit/die终止了程序执行,导致auto_append_file未加载执行,去掉exit/die就可以了。另外,如果提示:Unexpected system behaviour,可能是数据库连接不上,PDO、mbstring扩展未安装,响应数据里面带有错误信息的等等。

重启php-fpm:

#重启php-fpm
kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`

xhprof1

访问该服务器上php页面,即可以在http://192.168.84.2:8502/看到检测情况。
xhprof5
xhprof4
xhprof6

参数说明
Inclusive Time 包括子函数所有执行时间。
Exclusive Time/Self Time 函数执行本身花费的时间,不包括子树执行时间。
Wall Time 花去了的时间或挂钟时间。
CPU Time 用户耗的时间+内核耗的时间
Inclusive CPU 包括子函数一起所占用的CPU
Exclusive CPU 函数自身所占用的CPU

参考链接:
给CentOS6.3 + PHP5.3 安装PHP性能测试工具 XHProf-0.9.2
xhprof.io/INSTALL.md
php auto_append_file

开启Firephp时Nginx PHP-FPM下502错误解决

在自己的电脑上调试的好好的,部署到Linux Nginx环境的时候却发现有些PHP页面在Firefox下面会返回502,在IE下面却是正常的,甚至在chrome下面也会出现502。顺着Nginx,Firefox这两个关键字,终于找到原因:原来是开启Firephp(chrome装了webug)时,Firephp的调试信息会写入的请求头里面,导致Nginx的FastCGI缓冲区超出,从而返回502。在自己的电脑上之所以不会是因为使用的是Apache。
于是顺着其他人的解答修改了nginx.conf中fastcig相关的参数:

fastcgi_buffer_size 1024k;
fastcgi_buffers 8 512k;
fastcgi_busy_buffers_size 1024k;
fastcgi_temp_file_write_size 1024k;

一开始这些参数是256,后来改成了512还是有部分页面会出现502,再改成1024终于好了。 继续阅读