php实现秒杀系统

  • 转载
  • 发布时间: 1个月前
  • 收藏数: 0 / 点赞数: 0 / 阅读数: 18

压测工具

当我们将整个代码逻辑写完了之后,我们需要测试性能是怎么样的,这个时候就需要用到压测工具,压测工具选择比较多,但是他们的基本原理是相近的。

通过多线程的模式,并发的访问我们需要压测的接口,然后把结果汇总统计出来。

  • 安装压测工具
yum -y install httpd-tools

ab -V
  • 检查接口最大QPS
ab -c10 -n100  http://xxx

-c10:10个并发的线程去访问指定接口

-n100:总共访问100次

Requests per second:3673.55 [#/sec] (mean)

Nginx限流配置

通过限流的方式来确保我们的服务不会因为流量的暴增而出现雪崩的现象。

线上的服务器一般都是通过nginx来做限流的。

  • 按连接数限速,即并发数(ngxhttplimitconnmodule)

    我有多个客户端,每个客户端一个链接,通过限制连接数,来保证我们的服务是在安全可控的范围内运行的。

  • 按请求速率限速,按照ip限制单位时间内的请求数(ngxhttplimitreqmodule)

    我可以按照某一个ip限制它在单位时间内的请求数。

区别:

一个是限制并发数,一个是限制单个ip的请求数,并不能控制总的请求数。

这种方式所对应的nginx模块也是不一样的。

限流配置:

* limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;   //创建规则
* limit_req zone=mylimit burst=1 nodelay;     //应用规则

限流算法介绍

如果让我们自己去实现一个限流,该如何做呢?

  • 令牌桶算法
  • 漏桶算法
  • 计数器

单位时间计数器计数即可,一般在应用程序中写的较多

CDN

如果我们一个服务的QPS是固定的情况下,那么我们有没有办法来提升单服务的性能呢?

CDN就是提升单服务的利器。

  • CDN:内容分发网络
  • 缩短访问路径,减少源站压力、提高内容响应速度
  • 为源站提供安全保护

我们的请求是先到CDN服务器,如果CDN服务器没有的话,才会回源,因此如果我们有一些类似DDOS攻击的话,通过CDN可以很大程度上保护我们源站的安全。

CDN是通过域名解析的CNAME记录实现的。

CNAME记录 - 类似查询转发,该记录不能直接使用IP,只能是另一个主机的别名。

CDN是利用该记录来指定CDN服务器,如果有A记录与CNAME记录同时存在,则只使用A记录。

大型网站架构

Nginx负载均衡算法介绍

  • Round-robin(轮询)

负载均摊

  • Weight-round-robin(带权轮询)
upstream test {
    server 192.168.1.100:8080 weight=1;
    server 192.168.1.101:8080 weight=3;
}
  • Ip-hash(Ip哈希)

消息队列介绍

  • 消息队列实际为链表,头插尾出,先进先出(FIFO),高并发下容易发生阻塞。
  • 现实生活中应用:海底捞 排队

消息队列也分 实时队列 和 延时队列

消息队列的作用

  • 提高请求响应速度,如:创建订单后的流程,发push、短信提醒等
  • 瞬间高并发下,可起到削峰,如:双十一零点并发创建订单
  • 延时队列,时间维度任务触发,如:发货提醒

秒杀系统的重难点

秒杀系统使用场景

  • 商城活动抢购、优惠券、定时抢购
  • 小米商城手机抢购
  • 12306的抢票
  • 天猫双十一凌晨促销秒杀

这些秒杀场景中并发量远远大于有效写数据(有效订单)

秒杀系统的特点

  • 抢购人数远多于库存,读写并发巨大
  • 库存少,有效写少
  • 写需强一致性,商品不能超卖
  • 读强一致性要求不高

秒杀系统的难点

稳定性难

  • 高并发下,某个小依赖可能直接造成雪崩
  • 流量预期难精确,过高也造成雪崩
  • 分布式集群,机器多,出故障的概率高

准确性难

  • 库存、抢购成功数、创建订单数之间的一致性

高性能难

  • 有限成本下需要做到极致的性能

秒杀系统架构原则

稳定性

  • 减少第三方依赖,同时自身服务部署也需做到隔离
  • 压测、降级、限流方案、确保核心服务可用
  • 需健康度检查机制,整个链路避免单点

高性能

  • 缩短单请求访问路径、减少IO
  • 减少接口数、降低吞吐数据量、请求次数减少

目标

满足高并发且高可用的秒杀系统

秒杀系统基本需求分析

秒杀服务核心实现

我们应该怎么去设计?

  • 满足基本需求,做到单服务极致性能
  • 请求链路流量优化,从客户端到服务端每层优化
  • 稳定性建设

基本需求

  • 扣库存
  • 查库存、排队进度
  • 查订单详情、创建订单、支付订单

库存少、抢购人数远多于库存,读写并发高

场景示例

实现一个商城秒杀:

  • 某件商品有1000库存
  • 100w并发读
  • 100w并发抢购

扣库存方案:

  • 下单减库存?

并发请求 ---> 创建订单 ---> 扣库存 ---> 支付

问题:恶意下单,不支付库存卖不出去

好处:不会超卖

  • 支付减库存?

并发请求 ---> 创建订单 ---> 支付 ---> 扣库存

问题:订单超卖、订单支付不了

  • 预扣库存

并发请求 ---> 扣库存 ---> 创建订单 ---> 支付

问题:不支付库存卖不出

极高并发下怎么做到单服务极致性能?

实现极致性能其本质就是有效压榨CPU

  • 减少上下文切换

进程间切换、线程间的切换、系统调用都会进行上下文切换,cpu需要花时间去进行切换,所以进程、线程越多,cpu花在上下文切换的时间就越多,而花在有效计算上的时间就越少。

有没有办法减少cpu的上下文切换呢?

答案当然是肯定的,比如Go语言中的协程,协程往往是基于线程来实现的,协程把线程分成多个时间片,当遇到阻塞I/O,就可以让cpu执行其他的协程,这样实现上,我的线程没有进行切换,因此我们线程切换是比较少的。

其次,我们PHP的通用模型是php-fpm网络模型,它是属于多进程、阻塞式的网络模型,它并不是一个很好的高性能的服务,它在处理请求的时候,会出现频繁的进程间的切换。

  • 减少阻塞式I/O

当程序执行的时候是串行的。

比如,需要去执行一个sql时,程序阻塞在那里等待sql的执行结果。

我们php也是阻塞式I/O这种方式。因此,我们原生php在扣库存的时候并不是一个好的选型。

当然,php有一个很好的扩展 - swoole

swoole针对上下文切换 与 阻塞式I/O 做了很好的优化,也可以提供非常好的性能。

I/0主要包含

  • RPC调用 - 远程调用

调用http服务、mysql查询、redis查询都是远程调用

  • 磁盘读写

读写文件

无IO怎么做?

  • 拆解 - 扣库存与写订单分开

将写订单、支付订单耗时的动作分离开。

我们秒杀系统只提供一个扣库存的服务,将写订单、支付订单放到订单中心去处理,因为这些东西并不需要需要非常高性能的。

  • 用内存

我们要尽可能的用内存,比如,调用redis等等,redis可以单机支持10万qps

  • 用本地内存

普通下单业务实现

客户端 ---> server ---> DB(1.开启事务 2.减库存 3.创建订单 4.提交) ---> 响应客户端

去I/O后业务实现

客户端 ---> 减库存(本地减库存,减库存成功后,才将消息存入MQ后并立即返回响应客户端) ---> MQ(异步处理) ---> DB(创建订单)

扣库存分布式实现方案

并发量过大极致单服务还是扛不住怎么办?

通过分布式集群,将库存分摊到多台服务器,提高我们扣库存的能力。

当然多点服务还可以避免单点故障。

本地减库存,集群机器挂了怎么办?怎么保证不少卖

需要一台独立的统一库存。

  1. 初始化库存到本地库存
  2. 本地减库存,成功则进行统一减库存,失败则返回
  3. 统一减库存成功则写入MQ,异步创建订单
  4. 告知用户抢购成功

为什么统一减库存能防止少卖的现象?

统一库存如果为1000,有10个节点的本地库存,这10个节点库存总数必须大于1000,最理想的状态是,某个节点挂掉后,其它9个节点的库存加起来还能等于1000,如果没有节点挂点,所有的请求在统一库存那里也会被合理拦截,最终控制在1000,本地库存主要是为了减轻库存查询压力,大于等于实际的库存即可,但是不能超过太多。

创建、支付订单服务

  • 与扣库存服务隔离
  • 用户收到抢购成功、页面跳转到订单中心去支付订单

读商品信息页

  • 与库存服务隔离
  • 商品库-主多从提高读能力
  • 页面静态化 + 缓存 + db实现即可
评论