秒杀 及其 涉及生态

秒杀生态

涉及工具:

##1.压测工具

#安装
yum -y install httpd-tools
//检测是否安装
ab -V

#检测接口最大qps:
    ab -n1000 -c10 httpd://xxx    模拟并发10人 1000次请求

linux shell:
$ vim test.php //echo 'hello world';
$ crul test.cn/test.php  //检测是否可执行

$ ab -n1000 -c100 test.cn/test.php

//目的:
1.优化特定服务接口 提交接口效率
2.当优化服务已经做的很完善了  还有做限流: 关于限流=> nigix

##2.Nginx

nginx限流配置:

nginx限流:2种
按连接数,即并发数(ngx_http_limit_conn_module)
按请求速率,即IP限制单位时间内的请求数(ngx_http_limit_req_module)


限流配置(在nginx.conf里面进行配置) 先定义 => 后配置应用
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s // 定义规则
    (解读:我要以某一个key(一个用户的ip地址) 限制他的请求速率为1个请求每秒 给他申请的空间是10m的内存 名字为mylimit)
     limit_req zone=mylimit burst=1 nodelay; // 应用规则
    (解读:应用的限流规则名称为mylimit 没有后面的两个参数的话就会严格按照创建的规则进
	burst 可以放宽 1个的缓存空间  nodelay 是否等待(队列排队)

nginx限流配置:(样例)

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s

localhost ~\.php$ {

        limit_req zone=mylimit burst=1 nodelay;

        try_files $uri =404;

         .........
![](/uploads/article/20200902/5f4fba154c436.jpg)

##3.限流算法

1.令牌桶 2.漏桶 3. 计数器(应用程序中)

#####提升单服务利器 CDN:内容分发网络 ✦缩短访问路径,减少源站压力. 提高内容响应速度 ✦为源站提供安全保护

CDN架构 client -> 上海cdn -> 源服务器 <-北京cdn <- client2

4. 大型网站架构

解读:)( router(硬件路由器) //100w流量 lvs1(不解析包内容 tcp内容头修改) //几十万 (接入层) nginx(解析包内容) //(根据不同域名ip哈希->转发不同server 轮询 权重 分发到server) //几万 (server层) nginx + fpm //几千

nginx 负载均衡算法介绍

✦ Round-robin(轮循) ✦ Weight-round-robin(带权轮循) ✦ Ip-hash(Ip哈希)(不推荐 学校的客户端都是一个ip访问不合适) // 带权轮循 伪代码

###消息队列-介绍:

PHP秒杀(思路不分语言)

✦ vcode源于需求: 秒杀 比如100000w个人抢100个商品

Step 分析需求

✦ 特点 ✦ 难点痛点

✦架构原则 ---稳定性 及其 性能方面

Step2 核心实现

设计原则及可行优化:

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

基本需求

✦ 扣库存 //核心

✦ 查库存, 排队进度 //核心 依据核心做到单服务核心性能

✦ 查订单详情, 创建订单, 支付订单 //非核心 可分离 Core: 扣库存方案

:)流程上面涉及2个细节---

:)先扣除库存,然后创建订单, 支付

:)10分支内不支付则取消订单, 避免不支付库存卖不出问题

极致并发下?单服务极致性能 与 I/O 涉及

无I/O实现

并发极大单服务扛不住-> 多服务

:> 初始化库存到本地库存 (防止少卖 本地初始化可卖量 大于100 已买库存数量初始化0 ) :> 本地减库存, 成功则进行统一减库存, 失败则返回 :> 统一减库存成功则写入MQ, 异步创建订单 :> 告知用户抢购成功

不一定要用php 可以用php的swoole/hyperf 或者 GO
创建,支付订单服务

商品读取展示优化

:> 页面静态化 静态资源(cdn和缓存) 和 动态数据(db) 2种逻辑

读场景:

读商品详情 读库存(弱一致性 异步(远程读取到本地(数据库->本地))即可) 读排队进度(忽略)

###漏斗型流量

读库存优化

扣库存优化

:> 客户端防重入 (如按钮置灰 :> 客户端限制刷新频次 :> 验证码防刷消峰

:> 接入层限制同一用户请求次数 :> Server分布式分摊请求流量

<?php

//####### 本文秒杀示例代码 仅供思路参考
class Base
{
    static $redisObj;

    static function conRedis($config = array())
    {
        if (self::$redisObj) return self::$redisObj;
        self::$redisObj = new \Redis();
        self::$redisObj->connect("127.0.0.1", 6379);
        return self::$redisObj;
    }

    static function output($data = array(), $errNo = 0, $errMsg = 'ok')
    {
        $res['errno'] = $errNo;
        $res['errmsg'] = $errMsg;
        $res['data'] = $data;
        echo json_encode($res);exit();
    }
}

?>
  
<?php
include('base.php');
class Api extends Base
{
    //共享信息,存储在redis中,以hash表的形式存储,%s变量代表的是商品id
    static $userId;
    static $productId;

    static $REDIS_REMOTE_HT_KEY         = "product_%s";     //共享信息key
    static $REDIS_REMOTE_TOTAL_COUNT    = "total_count";    //商品总库存
    static $REDIS_REMOTE_USE_COUNT      = "used_count";     //已售库存
    static $REDIS_REMOTE_QUEUE          = "c_order_queue";  //创建订单队列

    static $APCU_LOCAL_STOCK    = "apcu_stock_%s";       //总共剩余库存

    static $APCU_LOCAL_USE      = "apcu_stock_use_%s";   //本地已售多少
    static $APCU_LOCAL_COUNT    = "apcu_total_count_%s"; //本地分库存分摊总数

    public function __construct($productId, $userId)
    {
        self::$REDIS_REMOTE_HT_KEY  = sprintf(self::$REDIS_REMOTE_HT_KEY, $productId);
        self::$APCU_LOCAL_STOCK     = sprintf(self::$APCU_LOCAL_STOCK, $productId);
        self::$APCU_LOCAL_USE       = sprintf(self::$APCU_LOCAL_USE, $productId);
        self::$APCU_LOCAL_COUNT     = sprintf(self::$APCU_LOCAL_COUNT, $productId);
        self::$APCU_LOCAL_COUNT     = sprintf(self::$APCU_LOCAL_COUNT, $productId);
        self::$userId               = $userId;
        self::$productId            = $productId;
    }
    static  function clear(){
	apcu_delete(self::$APCU_LOCAL_STOCK);
	apcu_delete(self::$APCU_LOCAL_USE);
	apcu_delete(self::$APCU_LOCAL_COUNT);
		
	}
    /*查剩余库存*/
    static function getStock()
    {
	$stockNum = apcu_fetch(self::$APCU_LOCAL_STOCK);
        if ($stockNum === false) {
            $stockNum = self::initStock();
        }
        self::output(['stock_num' => $stockNum]);
    }

    /*抢购-减库存*/
    static function buy()
    {
        $localStockNum = apcu_fetch(self::$APCU_LOCAL_COUNT);
        if ($localStockNum === false) {
            $localStockNum = self::init();//预留buf
        }

        $localUse = apcu_inc(self::$APCU_LOCAL_USE);//本已卖 + 1
        if ($localUse > $localStockNum) {//抢购失败 大部分流量在此被拦截
		echo 1;
            self::output([], -1, '该商品已售完');
        }

        //同步已售库存 + 1;
        if (!self::incUseCount()) {//改失败,返回商品已售完
            self::output([], -1, '该商品已售完');
        }

        //写入创建订单队列
        self::conRedis()->lPush(self::$REDIS_REMOTE_QUEUE, json_encode(['user_id' => self::$userId, 'product_id' => self::$productId]));
        //返回抢购成功
        self::output([], 0, '抢购成功,请从订单中心查看订单');
    }

    /*创建订单*/
    /*查询订单*/
    /*总剩余库存同步本地,定时执行就可以*/
    static function sync()
    {
	$data = self::conRedis()->hMGet(self::$REDIS_REMOTE_HT_KEY, [self::$REDIS_REMOTE_TOTAL_COUNT, self::$REDIS_REMOTE_USE_COUNT]);
        $num = $data['total_count'] - $data["used_count"];
        apcu_add(self::$APCU_LOCAL_STOCK, $num);
        self::output([], 0, '同步库存成功');
    }
    /*私有方法*/
    //库存同步
    private static function incUseCount()
    {
        //同步远端库存时,需要经过lua脚本,保证不会出现超卖现象
        $script = <<<eof
            local key = KEYS[1]
            local field1 = KEYS[2]
            local field2 = KEYS[3]
            local field1_val = redis.call('hget', key, field1)
            local field2_val = redis.call('hget', key, field2)
            if(field1_val>field2_val) then
                return redis.call('HINCRBY', key, field2,1)
            end
            return 0
eof;
        return self::conRedis()->eval($script,[self::$REDIS_REMOTE_HT_KEY,  self::$REDIS_REMOTE_TOTAL_COUNT, self::$REDIS_REMOTE_USE_COUNT] , 3);
    }
    /*初始化本地数据*/
    private static function init()
    {
        apcu_add(self::$APCU_LOCAL_COUNT, 150);
        apcu_add(self::$APCU_LOCAL_USE, 0);
    }
    static  function initStock(){
        $data = self::conRedis()->hMGet(self::$REDIS_REMOTE_HT_KEY, [self::$REDIS_REMOTE_TOTAL_COUNT, self::$REDIS_REMOTE_USE_COUNT]);
        $num = $data['total_count']- $data["used_count"];
        apcu_add(self::$APCU_LOCAL_STOCK, $num);
        return $num;
    }

}

try{
$act = $_GET['act'];
$product_id = $_GET['product_id'];
$user_id = $_GET['user_id'];

$obj = new Api($product_id, $user_id);
if (method_exists($obj, $act)) {
    $obj::$act();
    die;
}
echo 'method_error!';
} catch (\Exception $e) {
    echo 'exception_error!';
    var_dump($e);
}

总结

//学习笔记 from https://www.imooc.com/learn/1156

记录你我
请先登录后发表评论
  • latest comments
  • 总共0条评论