高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

超发的原因

假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。)

优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

<?php //优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回falseinclude('./mysql.php');$username = 'wang'.rand(0,1000);//生成唯一订单function build_order_no(){ return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0,$username){ global $conn; $sql="insert into ih_log(event,type,usernma) values('$event','$type','$username')"; return mysqli_query($conn,$sql);}function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number){ global $conn; $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number) values('$order_sn','$user_id','$goods_id','$sku_id','$price','$username','$number')"; return mysqli_query($conn,$sql);}//模拟下单操作//库存是否大于0$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' ";$rs=mysqli_query($conn,$sql);$row = $rs->fetch_assoc(); if($row['number']>0){//高并发下会导致超卖 if($row['number']0"; $store_rs=mysqli_query($conn,$sql); if($store_rs){ //生成订单 insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number); insertLog('库存减少成功',1,$username); }else{ insertLog('库存减少失败',2,$username); } }else{ insertLog('库存不够',3,$username); }?>

解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

优化方案2:使用MySQL的事务,锁住操作的行

<?php //优化方案2:使用MySQL的事务,锁住操作的行include('./mysql.php');//生成唯一订单号function build_order_no(){ return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0){ global $conn; $sql="insert into ih_log(event,type) values('$event','$type')"; mysqli_query($conn,$sql);}//模拟下单操作//库存是否大于0mysqli_query($conn,"BEGIN"); //开始事务$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行$rs=mysqli_query($conn,$sql);$row=$rs->fetch_assoc();if($row['number']>0){ //生成订单 $order_sn=build_order_no(); $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; $order_rs=mysqli_query($conn,$sql); //库存减少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'"; $store_rs=mysqli_query($conn,$sql); if($store_rs){ echo '库存减少成功'; insertLog('库存减少成功'); mysqli_query($conn,"COMMIT");//事务提交即解锁 }else{ echo '库存减少失败'; insertLog('库存减少失败'); }}else{ echo '库存不够'; insertLog('库存不够'); mysqli_query($conn,"ROLLBACK");}?>

3. FIFO队列思路

那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

4. 文件锁的思路

对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失

优化方案3:使用非阻塞的文件排他锁

如何用PHP写商品折扣_秒杀抢购时的超发,你用php如何优化的相关推荐

  1. 如何用python写串口通信软件_如何用python写个串口通信的程序?

    展开全部 打开串口后启动一个线程来监听串口数据的进入,有数据时,就做数据的处理. 用python写串口通信e68a84e8a2ad32313133353236313431303231363533313 ...

  2. 如何用python写数值运算_如何理解Python的数值运算?

    1 基本算术运算 1.1 使用规则 – Python解析器相当于一个简单的计算器 – Python解析器可以接受简单的算术表达式 – 运算符可以使加(+)减(-)乘(*)除(/) 1.2 实操理解 # ...

  3. 如何用python写小工具_用python写一个录音小工具

    Python的paramiko,wxPython库的应用 Sound eXchange 命令行 需求 最近在给一个做语音识别的项目做QA工作.众所周知,此类人工智能方面的项目都需要一些数据收集的工作. ...

  4. 如何用java写单链表_如何使用Java实现单链表?

    首先构建节点类: package com.fzw.sf; public class Node { private Object data; private Node next; Node(Object ...

  5. typora 公式_如何用Typora写Markdown上传至知乎

    如何用Typora写Markdown上传至知乎 [TOC] 起因 知乎自带的编辑器太恶心了,于是想要在typora本地编辑之后,使用知乎的上传md功能进行同步,但是发现会缺失图像和公式.优化思路如下: ...

  6. python剪刀石头布_如何用python写剪刀石头布

    如何用python写剪刀石头布 发布时间:2020-07-20 11:52:16 来源:亿速云 阅读:111 本篇文章给大家分享的是有关如何用python写剪刀石头布,小编觉得挺实用的,因此分享给大家 ...

  7. python中select模块_基于python select.select模块通信的实例讲解 如何用python写个串口通信的程序...

    python socket怎么利用select实现双工通信 方法: Before : 0000000000000000000000000000000000000000 After pack: 0100 ...

  8. 用python写用手机发邮件_如何用python写发邮件?

    原标题:如何用python写发邮件? 1. 163邮箱 163邮箱需要设置客户端授权密码 请输入图片描述 # coding:utf-8 from email.header import Header ...

  9. python小助手_如何用python写个人专属群聊提醒小助手?

    前言 大家还记得教会父母玩微信是什么时候吗?父母学会后,我们的生活就发生了「质」的变化,父母也许会吐槽你的微信头像不好,要你换一个头像. 最近 pk哥 又被母后大人吐槽了,原因是亲戚微信群里某个亲戚生 ...

最新文章

  1. SAP MM 分期付款场景下的付款方式
  2. python 日志不会按照日期分割_python实现日志按天分割
  3. 22种代码味道(Martin Fowler与Kent Beck)
  4. Dubbo 3.0 前瞻:重塑 Spring Cloud 服务治理
  5. 此时无法停用连接。这个连接可能在用一个或多个不支持即插即用的协议,或者它是由其他用户或系统帐户初始化的。...
  6. python-自动发邮件
  7. jcr多久更新一次_拼多多商品价格竞争力星级有什么用?多久更新一次?
  8. java代码中发送http请求中使用DnsResolver的问题
  9. request.setAttribute()怎么用的?
  10. iwconfig 安装_arm linux iwconfig 等
  11. 安卓逆向,Python爬虫,网页逆向和其他学习计划
  12. mysql中date类型_MySql的日期类型datetime和timestamp
  13. 【linux命令】cp复制命令
  14. 服务器性能监控之New Relic 入门教程
  15. 免费的可视化Web报表工具,JimuReport v1.5.0版本发布
  16. PBR渲染(四)——PBR头发渲染
  17. 滑动验证码自动化实现(1)
  18. 多标签分类、BCELoss和BCEWithLogitsLoss用法
  19. python里使用协程和StreamReader、StreamWriter来创建echo服务端
  20. 毛球修剪器单片机开发方案-衣物专属的美容师

热门文章

  1. linux acl 权限 给任何用户或用户组设置任何文件/目录的访问权限
  2. linux su、su -和sudo的区别
  3. linux 内核编译错误 Makefile:416: *** mixed implicit and normal rules: deprecated syntax
  4. Android开发--多媒体应用开发(一)--MediaPlayer的使用介绍
  5. kvm架构服务器_顺应云计算变革大势,腾讯云全新星星海自研服务器真正为云而生...
  6. 数据结构 - 如何判断两个无环单链表是否相交;如果相交,给出相交的第一个结点
  7. 2021高考萧山二中成绩查询,萧山中学2018高考成绩
  8. java进销存培训_Java实例学习——企业进销存管理系统(2)
  9. mysql脚本下载_Mysql备份脚本
  10. mysql在线复制_mysql的两种复制模式