并发抢购系统注意事项
高并发架构设计描述
程序端核心代码实现
订单流程mysql 端并发解决方案
注意事项
(1)高并发环境下,对于服务器cup、内存、网络宽带使用率会瞬间暴涨,需要注意对同服务器上其他应用的影响。(项目解耦,高并发应用独立部署)
(2)服务器高负载运行,容易出现死机,重启服务器场景,要提前考虑内存(redis)数据备份与恢复,防止用户抢购数据丢失.
(3)高并发应用首先要注重稳定性,其次是性能上优化.
(4) 一台服务器能够支持多少并发量
nginx服务为例:
worker_processes 8;
worker_rlimit_nofile 102400;
use epoll;
worker_connections 102400;
ulimit -n
cat /proc/sys/fs/file-max
架构设计
(1)LVS服务, 做负载均衡调度, 采用RD模式, 通过股修改数据包的目的MAC地址实现转发,该方式性能好, 对并发高应用,适合大规模部署负载均衡机器;抗负载能力强、是工作在网络4层仅作分发之用,没有流量的产生;工作稳定,自身有完整的双机热备方案
(2)keepalive(vrrp协议方式) 做心跳检测,支持应用具有高可用性。
(3)nginx工作在网络的7层,所以它可以针对http应用本身来做分流策略, 可用说对LVS负载的补充。nginx高效处理高并发请求在于采用异步非阻塞工作方式和epoll IO 模型。
(4)页面动态数据,用户数据,抢购商品数据采用Redis存储。
(5)用户抢购记录标识存储在Redis服务器端。在nginx负载均衡端,应用lua脚本做用户抢购记录过滤。
(6)real server端部署 nginx与php, 同时 real server 可以参与负载端调度。
(7)mysql server cluster 端采用一主多从部署,master负载数据写及同步到slave, slave负责数据读取。推荐应用mysql代理组件atlas, 实现对php端对mysql读写透明操作。
核心代码实现
背景
假设每个用户只允许抢购一件商品。
预备数据
抢购商品总数存入redis中, 比如十万个数据
$redisObj = new redis();
$redisObj->set('goods_amount', 1000000);
$redis->watch('goods_amount'); //应用redis watch 乐观锁
$amount = $redis->get('goods_amount');
if($amount > 0)
{
$userInfo = $reids->get('user_info_crc32(url_token)', array('userId'=>120, '....'));
if(empty($userInfo)){
$ret = $redis->multi() ->decr('goods_amount') ->exec();
if($ret){
$reids->set('user_info_crc32(url_token)', array('userId'=>120, '....'));
根据crc32(url_token)唯一索引创建改用户已抢过商品的标识。(同时标识可以设置一段时间有效期,例如10分钟);
write("user_id", {user_id}_success.log);
}else {
//提示抢购失败
}
} else {
$redis->unwatch(‘goods_amount');
//提示抢购失败
} else {
//抢购结束, 封闭入口
}
}
(1)下一个抢购请求到来时,在nginx服务器lua端,检查googs_amount抢购商品数量,判断抢购有没有结束,在判断user_info_crc32(url_token)有没有抢过成功,如果成功跳转到下单页面,否则执行抢过流程。
(2)抢购首页直接高并发静态资源存储在cdn 服务端, 来减轻服务端访问请求的压力
mysql端并发解决
(1)抢购商品数据预热,提前存储在redis中,比如商品名称,属性等等。
(2)采用innodb 数据库引擎,在高并发场景读操作有优势,合理创建表结构,尽可能的减少链表查,可以适当设计表中冗余字段,sql查询能够必须走索引。
(3)用户浏览商品详情页(需要在redis端做动态数据缓存)
(4)用户点击购买跳转到订单详情页(包括用户基本信息,商品信息,支付方式,积分消费等数据考虑对数据库并发查询压力,要采用redis缓存策略)
(5)订单数据提前生成,user_id留空,同时通过redis lpush,把连续订单id,提前同步到redis分布式集群,redis集群支持心调检测,能够自动做服务奔溃切换。
(6)用户提交订单后, 在redis服务lpop拿到一个订单id, 根据订单id条件更新用户user_id等信息。
begin;
update mt_account set user_id=100 where order_id=$orderId and user_id=0 li mit 1;
commit;