长弓张 發表於 2019-6-18 15:44:00

php结合redis实现高并发下的抢购、秒杀功能

<p>抢购、秒杀是平常很常见的场景,面试的时候面试官也经常会问到,比如问你淘宝中的抢购秒杀是怎么实现的等等。</p>
<p>抢购、秒杀实现很简单,但是有些问题需要解决,主要针对两个问题:</p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>1 高并发对数据库产生的压力</strong></span></p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>2 竞争状态下如何解决库存的正确减少("超卖"问题)</strong></span></p>
<p>第一个问题,对于PHP来说很简单,用缓存技术就可以缓解数据库压力,比如memcache,redis等缓存技术。</p>
<p>第二个问题就比较复杂点:</p>
<p><strong>常规写法:</strong></p>
<p>查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数</p>
<div class="cnblogs_code">
<pre>&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$conn</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_connect</span>("localhost","big","123456"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">echo</span> "connect failed"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">exit</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 128, 1)">mysql_select_db</span>("big",<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("set names utf8"<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(128, 0, 128, 1)">$price</span>=10<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$user_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$goods_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$sku_id</span>=11<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$number</span>=1<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成唯一订单</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> build_order_no(){
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 128, 128, 1)">date</span>('ymd').<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">implode</span>(<span style="color: rgba(0, 0, 255, 1)">NULL</span>, <span style="color: rgba(0, 128, 128, 1)">array_map</span>('ord', <span style="color: rgba(0, 128, 128, 1)">str_split</span>(<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">uniqid</span>(), 7, 13), 1))), 0, 8<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">记录日志</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> insertLog(<span style="color: rgba(128, 0, 128, 1)">$event</span>,<span style="color: rgba(128, 0, 128, 1)">$type</span>=0<span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">global</span> <span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_log(event,type)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$event</span>','<span style="color: rgba(128, 0, 128, 1)">$type</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">模拟下单操作
//库存是否大于0</span>
<span style="color: rgba(128, 0, 128, 1)">$sql</span>="select number from ih_store where goods_id='<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>' and sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>'";<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">解锁 此时ih_store数据中goods_id='$goods_id' and sku_id='$sku_id' 的数据被锁住(注3),其它事务必须等待此次事务 提交后才能执行</span>
<span style="color: rgba(128, 0, 128, 1)">$rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$row</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_fetch_assoc</span>(<span style="color: rgba(128, 0, 128, 1)">$rs</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(128, 0, 128, 1)">$row</span>['number']&gt;0){<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">高并发下会导致超卖</span>
    <span style="color: rgba(128, 0, 128, 1)">$order_sn</span>=<span style="color: rgba(0, 0, 0, 1)">build_order_no();
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成订单 </span>
    <span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$order_sn</span>','<span style="color: rgba(128, 0, 128, 1)">$user_id</span>','<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>','<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>','<span style="color: rgba(128, 0, 128, 1)">$price</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$order_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">库存减少</span>
    <span style="color: rgba(128, 0, 128, 1)">$sql</span>="update ih_store set number=number-{<span style="color: rgba(128, 0, 128, 1)">$number</span>} where sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>'"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$store_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 128, 128, 1)">mysql_affected_rows</span><span style="color: rgba(0, 0, 0, 1)">()){
      insertLog(</span>'库存减少成功'<span style="color: rgba(0, 0, 0, 1)">);
    }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
      insertLog(</span>'库存减少失败'<span style="color: rgba(0, 0, 0, 1)">);
    }
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
    insertLog(</span>'库存不够'<span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>出现这种情况怎么办呢?来看几种优化方法:</p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>优化方案1:</strong></span>将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">库存减少</span>
<span style="color: rgba(128, 0, 128, 1)">$sql</span>="update ih_store set number=number-{<span style="color: rgba(128, 0, 128, 1)">$number</span>} where sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>' and number&gt;0"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$store_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 128, 128, 1)">mysql_affected_rows</span><span style="color: rgba(0, 0, 0, 1)">()){
   insertLog(</span>'库存减少成功'<span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p><strong><span style="color: rgba(255, 0, 0, 1)">优化方案2</span>:</strong>使用<span style="color: rgba(255, 0, 0, 1)"><strong>MySQL</strong></span>的事务,锁住操作的行</p>
<div class="cnblogs_code">
<pre>&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$conn</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_connect</span>("localhost","big","123456"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">echo</span> "connect failed"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">exit</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 128, 1)">mysql_select_db</span>("big",<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("set names utf8"<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(128, 0, 128, 1)">$price</span>=10<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$user_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$goods_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$sku_id</span>=11<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$number</span>=1<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成唯一订单号</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> build_order_no(){
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 128, 128, 1)">date</span>('ymd').<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">implode</span>(<span style="color: rgba(0, 0, 255, 1)">NULL</span>, <span style="color: rgba(0, 128, 128, 1)">array_map</span>('ord', <span style="color: rgba(0, 128, 128, 1)">str_split</span>(<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">uniqid</span>(), 7, 13), 1))), 0, 8<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">记录日志</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> insertLog(<span style="color: rgba(128, 0, 128, 1)">$event</span>,<span style="color: rgba(128, 0, 128, 1)">$type</span>=0<span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">global</span> <span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_log(event,type)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$event</span>','<span style="color: rgba(128, 0, 128, 1)">$type</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">模拟下单操作
//库存是否大于0</span>
<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("BEGIN");   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开始事务</span>
<span style="color: rgba(128, 0, 128, 1)">$sql</span>="select number from ih_store where goods_id='<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>' and sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>' FOR UPDATE";<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行</span>
<span style="color: rgba(128, 0, 128, 1)">$rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$row</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_fetch_assoc</span>(<span style="color: rgba(128, 0, 128, 1)">$rs</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(128, 0, 128, 1)">$row</span>['number']&gt;0<span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成订单 </span>
    <span style="color: rgba(128, 0, 128, 1)">$order_sn</span>=<span style="color: rgba(0, 0, 0, 1)">build_order_no();
    </span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$order_sn</span>','<span style="color: rgba(128, 0, 128, 1)">$user_id</span>','<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>','<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>','<span style="color: rgba(128, 0, 128, 1)">$price</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$order_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">库存减少</span>
    <span style="color: rgba(128, 0, 128, 1)">$sql</span>="update ih_store set number=number-{<span style="color: rgba(128, 0, 128, 1)">$number</span>} where sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>'"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$store_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 128, 128, 1)">mysql_affected_rows</span><span style="color: rgba(0, 0, 0, 1)">()){
      insertLog(</span>'库存减少成功'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("COMMIT");<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">事务提交即解锁</span>
    }<span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
      insertLog(</span>'库存减少失败'<span style="color: rgba(0, 0, 0, 1)">);
    }
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
    insertLog(</span>'库存不够'<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("ROLLBACK"<span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>优化方案3:</strong></span>使用非阻塞的文件排他锁</p>
<div class="cnblogs_code">
<pre>&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$conn</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_connect</span>("localhost","root","123456"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">echo</span> "connect failed"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">exit</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 128, 1)">mysql_select_db</span>("big-bak",<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("set names utf8"<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(128, 0, 128, 1)">$price</span>=10<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$user_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$goods_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$sku_id</span>=11<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$number</span>=1<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成唯一订单号</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> build_order_no(){
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 128, 128, 1)">date</span>('ymd').<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">implode</span>(<span style="color: rgba(0, 0, 255, 1)">NULL</span>, <span style="color: rgba(0, 128, 128, 1)">array_map</span>('ord', <span style="color: rgba(0, 128, 128, 1)">str_split</span>(<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">uniqid</span>(), 7, 13), 1))), 0, 8<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">记录日志</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> insertLog(<span style="color: rgba(128, 0, 128, 1)">$event</span>,<span style="color: rgba(128, 0, 128, 1)">$type</span>=0<span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">global</span> <span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_log(event,type)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$event</span>','<span style="color: rgba(128, 0, 128, 1)">$type</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(128, 0, 128, 1)">$fp</span> = <span style="color: rgba(0, 128, 128, 1)">fopen</span>("lock.txt", "w+"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 128, 128, 1)">flock</span>(<span style="color: rgba(128, 0, 128, 1)">$fp</span>,LOCK_EX |<span style="color: rgba(0, 0, 0, 1)"> LOCK_NB)){
    </span><span style="color: rgba(0, 0, 255, 1)">echo</span> "系统繁忙,请稍后再试"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">下单</span>
<span style="color: rgba(128, 0, 128, 1)">$sql</span>="select number from ih_store where goods_id='<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>' and sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>'"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$row</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_fetch_assoc</span>(<span style="color: rgba(128, 0, 128, 1)">$rs</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(128, 0, 128, 1)">$row</span>['number']&gt;0){<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">库存是否大于0
    //模拟下单操作 </span>
    <span style="color: rgba(128, 0, 128, 1)">$order_sn</span>=<span style="color: rgba(0, 0, 0, 1)">build_order_no();
    </span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$order_sn</span>','<span style="color: rgba(128, 0, 128, 1)">$user_id</span>','<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>','<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>','<span style="color: rgba(128, 0, 128, 1)">$price</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$order_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">库存减少</span>
    <span style="color: rgba(128, 0, 128, 1)">$sql</span>="update ih_store set number=number-{<span style="color: rgba(128, 0, 128, 1)">$number</span>} where sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>'"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$store_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 128, 128, 1)">mysql_affected_rows</span><span style="color: rgba(0, 0, 0, 1)">()){
      insertLog(</span>'库存减少成功'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 128, 128, 1)">flock</span>(<span style="color: rgba(128, 0, 128, 1)">$fp</span>,LOCK_UN);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">释放锁</span>
    }<span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
      insertLog(</span>'库存减少失败'<span style="color: rgba(0, 0, 0, 1)">);
    }
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
    insertLog(</span>'库存不够'<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 128, 1)">fclose</span>(<span style="color: rgba(128, 0, 128, 1)">$fp</span>);</pre>
</div>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>优化方案4:</strong></span>使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是)</p>
<p>先将商品库存如队列</p>
<div class="cnblogs_code">
<pre>&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$store</span>=1000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$redis</span>=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Redis();
</span><span style="color: rgba(128, 0, 128, 1)">$result</span>=<span style="color: rgba(128, 0, 128, 1)">$redis</span>-&gt;connect('127.0.0.1',6379<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$res</span>=<span style="color: rgba(128, 0, 128, 1)">$redis</span>-&gt;llen('goods_store'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 128, 1)">$res</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$count</span>=<span style="color: rgba(128, 0, 128, 1)">$store</span>-<span style="color: rgba(128, 0, 128, 1)">$res</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">for</span>(<span style="color: rgba(128, 0, 128, 1)">$i</span>=0;<span style="color: rgba(128, 0, 128, 1)">$i</span>&lt;<span style="color: rgba(128, 0, 128, 1)">$count</span>;<span style="color: rgba(128, 0, 128, 1)">$i</span>++<span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(128, 0, 128, 1)">$redis</span>-&gt;lpush('goods_store',1<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">echo</span> <span style="color: rgba(128, 0, 128, 1)">$redis</span>-&gt;llen('goods_store');</pre>
</div>
<p>抢购、描述逻辑</p>
<div class="cnblogs_code">
<pre>&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(128, 0, 128, 1)">$conn</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_connect</span>("localhost","big","123456"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">echo</span> "connect failed"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">exit</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 128, 1)">mysql_select_db</span>("big",<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>("set names utf8"<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(128, 0, 128, 1)">$price</span>=10<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$user_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$goods_id</span>=1<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$sku_id</span>=11<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$number</span>=1<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成唯一订单号</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> build_order_no(){
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 128, 128, 1)">date</span>('ymd').<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">implode</span>(<span style="color: rgba(0, 0, 255, 1)">NULL</span>, <span style="color: rgba(0, 128, 128, 1)">array_map</span>('ord', <span style="color: rgba(0, 128, 128, 1)">str_split</span>(<span style="color: rgba(0, 128, 128, 1)">substr</span>(<span style="color: rgba(0, 128, 128, 1)">uniqid</span>(), 7, 13), 1))), 0, 8<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">记录日志</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> insertLog(<span style="color: rgba(128, 0, 128, 1)">$event</span>,<span style="color: rgba(128, 0, 128, 1)">$type</span>=0<span style="color: rgba(0, 0, 0, 1)">){
    </span><span style="color: rgba(0, 0, 255, 1)">global</span> <span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_log(event,type)
    values('</span><span style="color: rgba(128, 0, 128, 1)">$event</span>','<span style="color: rgba(128, 0, 128, 1)">$type</span>')"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">模拟下单操作
//下单前判断redis队列库存量</span>
<span style="color: rgba(128, 0, 128, 1)">$redis</span>=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Redis();
</span><span style="color: rgba(128, 0, 128, 1)">$result</span>=<span style="color: rgba(128, 0, 128, 1)">$redis</span>-&gt;connect('127.0.0.1',6379<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$count</span>=<span style="color: rgba(128, 0, 128, 1)">$redis</span>-&gt;lpop('goods_store'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(128, 0, 128, 1)">$count</span><span style="color: rgba(0, 0, 0, 1)">){
    insertLog(</span>'error:no store redis'<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成订单 </span>
<span style="color: rgba(128, 0, 128, 1)">$order_sn</span>=<span style="color: rgba(0, 0, 0, 1)">build_order_no();
</span><span style="color: rgba(128, 0, 128, 1)">$sql</span>="<span style="color: rgba(0, 0, 0, 1)">insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
values('</span><span style="color: rgba(128, 0, 128, 1)">$order_sn</span>','<span style="color: rgba(128, 0, 128, 1)">$user_id</span>','<span style="color: rgba(128, 0, 128, 1)">$goods_id</span>','<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>','<span style="color: rgba(128, 0, 128, 1)">$price</span>')"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$order_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">库存减少</span>
<span style="color: rgba(128, 0, 128, 1)">$sql</span>="update ih_store set number=number-{<span style="color: rgba(128, 0, 128, 1)">$number</span>} where sku_id='<span style="color: rgba(128, 0, 128, 1)">$sku_id</span>'"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$store_rs</span>=<span style="color: rgba(0, 128, 128, 1)">mysql_query</span>(<span style="color: rgba(128, 0, 128, 1)">$sql</span>,<span style="color: rgba(128, 0, 128, 1)">$conn</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 128, 128, 1)">mysql_affected_rows</span><span style="color: rgba(0, 0, 0, 1)">()){
    insertLog(</span>'库存减少成功'<span style="color: rgba(0, 0, 0, 1)">);
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
    insertLog(</span>'库存减少失败'<span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>上述只是简单模拟高并发下的抢购,真实场景要比这复杂很多,很多注意的地方</p>
<p>如抢购页面做成静态的,通过ajax调用接口</p>
<p>再如上面的会导致一个用户抢多个,思路:</p>
<p>需要一个排队队列和抢购结果队列及库存队列。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在,则已抢购,否则未抢购,库存减1,写<strong>数据库</strong>,将用户入结果队列。</p><br><br>
来源:https://www.cnblogs.com/huanglei559/p/11045516.html
頁: [1]
查看完整版本: php结合redis实现高并发下的抢购、秒杀功能