精华内容
下载资源
问答
  • PHP解决并发问题

    万次阅读 多人点赞 2020-05-27 12:10:04
    我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台...

    举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常。突然,这个路口1秒钟只能通过4部车,车流量仍然依旧,结果必定出现大塞车。(5条车道忽然变成4条车道的感觉)

    同理,某一个秒内,20*500个可用连接进程都在满负荷工作中,却仍然有1万个新来请求,没有连接进程可用,系统陷入到异常状态也是预期之内。

    14834077821.jpg

    其实在正常的非高并发的业务场景中,也有类似的情况出现,某个业务请求接口出现问题,响应时间极慢,将整个Web请求响应时间拉得很长,逐渐将Web服务器的可用连接数占满,其他正常的业务请求,无连接进程可用。

    更可怕的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环最终导致“雪崩”(其中一台Web机器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环),将整个Web系统拖垮。

    重启与过载保护

    如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

    秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回

    高并发下的数据安全

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

    1. 超发的原因

    假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

    14834077822.jpg

    在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

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

     <?php
     //优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
     include('./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']<$number){
             return insertLog('库存不够',3,$username);
           }
           $order_sn=build_order_no();
           //库存减少
           $sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and 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);
       }
     ?>
    
    1. 悲观锁思路

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

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

    14834077833.jpg

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

    优化方案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);
     }
     //模拟下单操作
     //库存是否大于0
     mysqli_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");
     }
     ?>
    
    1. FIFO队列思路

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

    14834077834.jpg

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

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

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

     <?php
     //优化方案4:使用非阻塞的文件排他锁
     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);
     }
     $fp = fopen("lock.txt", "w+");
     if(!flock($fp,LOCK_EX | LOCK_NB)){
         echo "系统繁忙,请稍后再试";
         return;
     }
     //下单
     $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){//库存是否大于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('库存减少成功');
             flock($fp,LOCK_UN);//释放锁
         }else{
           echo '库存减少失败';
             insertLog('库存减少失败');
         }
     }else{
       echo '库存不够';
         insertLog('库存不够');
     }
     fclose($fp);
      ?>
    
     <?php
     //优化方案4:使用非阻塞的文件排他锁
     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);
     }
     $fp = fopen("lock.txt", "w+");
     if(!flock($fp,LOCK_EX | LOCK_NB)){
         echo "系统繁忙,请稍后再试";
         return;
     }
     //下单
     $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){//库存是否大于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('库存减少成功');
             flock($fp,LOCK_UN);//释放锁
         }else{
           echo '库存减少失败';
             insertLog('库存减少失败');
         }
     }else{
       echo '库存不够';
         insertLog('库存不够');
     }
     fclose($fp);
      ?>
    
    1. 乐观锁思路

    这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

    在这里插入图片描述
    有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

    优化方案5:Redis中的watch

     <?php
     $redis = new redis();
      $result = $redis->connect('127.0.0.1', 6379);
      echo $mywatchkey = $redis->get("mywatchkey");
     /*
       //插入抢购数据
      if($mywatchkey>0)
      {
          $redis->watch("mywatchkey");
       //启动一个新的事务。
         $redis->multi();
        $redis->set("mywatchkey",$mywatchkey-1);
        $result = $redis->exec();
        if($result) {
           $redis->hSet("watchkeylist","user_".mt_rand(1,99999),time());
           $watchkeylist = $redis->hGetAll("watchkeylist");
             echo "抢购成功!<br/>";
             $re = $mywatchkey - 1;  
             echo "剩余数量:".$re."<br/>";
             echo "用户列表:<pre>";
             print_r($watchkeylist);
        }else{
           echo "手气不好,再抢购!";exit;
        } 
      }else{
          // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");
          //  $watchkeylist = $redis->hGetAll("watchkeylist");
             echo "fail!<br/>";   
             echo ".no result<br/>";
             echo "用户列表:<pre>";
           //  var_dump($watchkeylist); 
      }*/
     $rob_total = 100;   //抢购数量
     if($mywatchkey<=$rob_total){
         $redis->watch("mywatchkey");
         $redis->multi(); //在当前连接上启动一个新的事务。
         //插入抢购数据
         $redis->set("mywatchkey",$mywatchkey+1);
         $rob_result = $redis->exec();
         if($rob_result){
              $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey);
             $mywatchlist = $redis->hGetAll("watchkeylist");
             echo "抢购成功!<br/>";
           
             echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";
             echo "用户列表:<pre>";
             var_dump($mywatchlist);
         }else{
               $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao');
             echo "手气不好,再抢购!";exit;
         }
     }
     ?>
    
    展开全文
  • 场景: 类似银行存取,增减账户余额,...更新同一条记录可以通过增加version字段解决并发同时写的问题 但是明细表中先读取原先余额再写入新余额,这个怎么解决,怎么能让每次读出余额是最新的,写入明细之前余额不会被修改
  • 在 Asp.net MVC 2 中解决页面提交数据并发问题  通过本篇,你能了解到 Asp.net MVC 模型绑定处理过程,一种解决并发颗粒度到一条数据的方法.    * 如何解决互联网中某条数据的并发问题  在一...

    在 Asp.net MVC 2 中解决页面提交数据并发问题

         通过本篇,你能了解到 Asp.net MVC 模型绑定处理过程,一种解决并发颗粒度到一条数据的方法.

     

        * 如何解决互联网中某条数据的并发问题
        在一个页面提交数据库前把从数据库取出的数据和提交时数据库中的数据比较,不同则给出提示.
        在和其他童鞋的讨论中,都明确的指出了采用HashCode的方案,HashCode作为区分对象不同的一种方案,确实可行,但也有反对的声音.采用HashCode难免会涉及到对于实体对象的HashCode生成的问题,而实体对象的Hash又是根据自身属性生成,而属性又包含了其他关系,这种连带作用十分可观,也想过采用对于所有值属性采用Hash生成,对于关系对象着获取ID后再生成,但表数量也不少,如果考虑反射则每次提交都要遍历所有属性,性能客观,这还仅仅只是一次修改呢,折腾再三有了这样的方法.
        在每张表中加入一个新的DateTime字段,每次修改赋最新值.
        对,就是这么简单,每次读取时获得DateTime对象,返回到客户端页面,提交时再和数据库中对象比较.DateTime对象精确到毫秒,这种程度已经足够应付需求中所定义的并发.

     


    通过Ticks构造的DateTime对象
     

     

        *如何在Asp.net MVC中使用DateTime对象作为验证数据是否已过期依据

         Asp.net MVC 的模型肩负着承载数据的责任.同时又很容易配置,使用.下面这张图介绍了Asp.net MVC如何处理实体对象模型的(截自Pro Asp.net MVC 2 Framework.

     

        

         模型作为.net对象传递给视图后经过渲染后,呈现在HTML中.这就是回应客户端响应的流程.

         同样的,作为客户端,以单纯的字符串是无法直接在.net中使用的,又需要进行模型绑定.才能转换为.net对象,继而使用他.

     

         知道了整个模型的整个流程后,我们有了几个对策,思考再三决定传递DateTime对象的Ticks作为依据,因为直接输出DateTime最多也就精确到秒,不能和数据库中的原始数据DateTime匹配.所以获得其Ticks,作为字符串传输,然后返回的时候再解析.有了解决思路后就好做了.

        这是我们的模型,注意其中的HashTime可空属性. 注意他标上了一个UIHint Attribute,能够绑定指定的用户控件,继而单独的渲染该属性.

    代码
     1     public partial class ModelSimpleChangeUser : IHashTime
     2     {
     3         [HiddenInput]
     4         [DisplayName("用户编号")]
     5         [Required]
     6         public int UserId { getset; }
     7 
     8         [Required(ErrorMessage="名称必须不为空")]
     9         [DisplayName("用户名")]
    10         [DataType(System.ComponentModel.DataAnnotations.DataType.Text)]
    11         public string UserName { getset; }
    12         [Required(ErrorMessage="Email必须存在")]
    13         [DisplayName("电子邮件")]
    14         [DataType(System.ComponentModel.DataAnnotations.DataType.EmailAddress)]
    15         public string Email { getset; }
    16 
    17         [HiddenInput(DisplayValue = false)]
    18         [UIHint("HiddenDateTime")]
    19         [Required(ErrorMessage="HashTime必须存在")]
    20         public DateTime? HashTime { getset; }
    21 
    22 
    23 
    24         public ModelSimpleChangeUser() { }
    25         public ModelSimpleChangeUser(ES.DAL.User u)
    26         {
    27             this.UserId = u.UserId;
    28             this.Email = u.Email;
    29             this.HashTime = u.HashTime;
    30             this.UserName = u.UserName;
    31         }
    32 
    33         public ES.DAL.User ChangeTo(ES.DAL.User user)
    34         {
    35             if(UserId == user.UserId && user.HashTime.HasValue)
    36             {
    37                 user.UserName = UserName;
    38                 user.Email = Email;
    39             }
    40             return user;
    41         }
    42     }

     

        整个编辑视图也就一行起作用的代码.之所以能够这样是因为Asp.net MVC 2强大的模型特性标记功能,他能够在模型中声明如何显示,这就使得视图和模型完全分离,在视图里我都没有内联ViewPage的泛型类.使得页面可以编写完全独立的效果,而不必纠缠于和模型属性配对的问题中.

    <%= Html.EditorForModel() %>

     

         在HiddenDateTime控件实现了一个简单的从当前实体模型对象读取Ticks的功能,由于是可空DateTime,所以处理了为null情况(标记字段在测试的时候有些是Null,所以这里给处理了一下,如果在真实的环境中这是不存在的,因为在建立和修改时都已经自动赋值了)

     

    代码
    1 <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>" %>
    2 
    3 <%
    4     Dictionary<stringobject> others = new Dictionary<stringobject>();
    5     //others.Add("Oth", "ccc");    
    6 %>
    7 <%= Html.Hidden("", Model.HasValue ? Model.Value.Ticks : -1, others)%>

     

         效果如下:

     

     

         然后是绑定从客户端发来的数据问题.在这个点上费了很久,后来还是在书上找到了解决办法.
         首先要知道Asp.net MVC是从那些地方以怎样的顺序获取了数据,下面是获取的顺序.

     

    1. Request.Form 
    2. RouteData.Values
    3. Request.QueryString
    4. Request.Files
    5null

     

     

         前台输出的是DateTime的Ticks而不是默认的DateTime输出,所以还需要自己解析下.这里有一段ValueProviderFactroy的代码是截自书中的,在Global.asax的Appcation_Start事件注册后,就能截获所有参数的传递了.

        

     

    1         protected void Application_Start()
    2         {
    3             AreaRegistration.RegisterAllAreas();
    4 
    5             RegisterRoutes(RouteTable.Routes);
    6 
    7             ValueProviderFactories.Factories.Insert(0new ES.WEB.Models.HiddentTimeValueProviderFactory());
    8         }

        下面是那个解析工厂

    代码
        public class HiddentTimeValueProviderFactory : ValueProviderFactory
        {
            
    public override IValueProvider GetValueProvider(ControllerContext ctx)
            {
                
    return new HiddentTimeValueProvider(ctx.HttpContext.Request);
            }

            
    private class HiddentTimeValueProvider : IValueProvider
            {
                
    private HttpRequestBase request;
                
    public HiddentTimeValueProvider(HttpRequestBase Request)
                {
                    request 
    = Request;
                }

                
    public bool ContainsPrefix(string prefix)
                {
                    
    return "HashTime".Equals(prefix, StringComparison.OrdinalIgnoreCase);
                }

                
    public ValueProviderResult GetValue(string key)
                {
                    var result 
    = ContainsPrefix(key)
                      
    ? new ValueProviderResult(new DateTime(long.Parse(request["HashTime"])), null, CultureInfo.CurrentCulture)
                      : 
    null;
                    
    return result;
                }
            }
        } 

     

         自此我们的代码已经完全可以跑起来了,在控制器里对传过来的数据直接分析,看Ticks是否相等就能判断有并发情况了.当然在返回错误的视图中我们也做了一些改善用户体验的处理.

         下面是我们的控制器:

     

     1         public ActionResult Edit(Models.ModelSimpleChangeUser users)
     2         {
     3             return DoSafe(() =>
     4                 {
     5                     var u = UserManager.FindById(users.UserId);
     6                     if (u.HashTime != null)
     7                         if(users.HashTime != null &&
     8                            users.HashTime.Value.Ticks == u.HashTime.Value.Ticks)
     9                         {
    10                             u.UserName = users.UserName;
    11                             u.Email = users.Email;
    12                             UserManager.Update(u);
    13                             return RedirectToAction("Index"as ActionResult;
    14                         }
    15                     ViewData["lastAction"= "Edit";
    16                     ViewData["lastController"= "User";
    17                     return View(RetiredPage, users);
    18                 });
    19         }

     

         注意15,16行,他将当前处理的Controller和Action的名称都保存了起来,以备在视图中使用,同样将用户输入的数据返回了出去.

     

        下面是视图页面:

     1 <asp:Content ID="RetiredMain" ContentPlaceHolderID="MainContent" runat="server">
     2 
     3     <h2>对不起,您访问的页面已过期!</h2>
     4     <p>
     5         您所操作的原始数据可能被更改,请您返回获取最新数据.
     6     </p>
     7     <%= Html.ActionLink("返回",
     8                         ViewData[BaseController.LastAction].ToString(),
     9                         ViewData[BaseController.LastController].ToString(),
    10                         Model, new { id = "PostBackLink"}) %>
    11     <script type="text/javascript">
    12         setTimeout(function () {
    13             var g = document.getElementById("PostBackLink");
    14             try { g.click(); } catch (ex) { }
    15         }, 2000);
    16     </script>
    17 </asp:Content>

       

        用HtmlHelper生成了一个超链接,使用了我们之前保存的控制器数据,从而得知是在哪里引发了错误,并且还保存了用户输入的数据模型,最后给这个超链接赋值了一个id属性,供我们在客户端脚本上使用.

        在客户端脚本里我们触发了一个延迟脚本,两秒后触发返回超链接的click事件,由于在FireFox下没有该对象,所以只是简单的容错了一下,还有更多FireFox下的超链接的脚本触发请移步JavaScript模拟用户单击事件.

        整个过程还是相当简单的,不过条条大路通罗马,不一定都要坐技术含量高的飞机嘛.
        前些天分享了一个封装了EntityFramework的操作的类,这里又拓展了他,将HashTime自动写入,这里就没考虑泛型,因为数据的读写实在太频繁了,就将所有实体继承一个IHashTime接口,接口只有一个HashTime属性,然后封装了一个方法,在每次数据创建或者改写的时候调用.从而整个并发问题迎刃而解.

    1 private void setHashTime(object o)
    2         {
    3             (o as IHashTime).HashTime = DateTime.Now;
    4         }
    展开全文
  • 解决并发导致数据异常问题

    千次阅读 2018-11-15 11:05:50
    通常我们数据异常是由于并发导致的,那么我们如何避免这种问题呢? 举例: 当用户买充值卡时,我们一般会先查取数据库,然后改数据状态,给用户返回充值卡号,那问题来了 当同一时刻的用户一起购买充值卡时,就...

    通常我们数据异常是由于并发导致的,那么我们如何避免这种问题呢?

    举例:

    当用户买充值卡时,我们一般会先查取数据库,然后改数据状态,给用户返回充值卡号,那问题来了

    当同一时刻的用户一起购买充值卡时,就可能造成买到同一个充值卡,从而导致数据异常

    解决方案:

    1、如果并发并不是很高的情况下,我们可以考虑悲观锁,利用排它锁我们可以来使数据保持正常,但是在高并发的情况下,有可         能会导致等待的程序过多而增加服务器负荷,使之崩溃!

    2、考虑到解决方案1的问题,我们可以先修改数据的状态,然后再查取数据给用户返回,那这样的话我们获取修改数据的id就是关       键了,我们可以用mysql中的一个函数来解决这个问题,例如:“update table set 'status'=1,'id'=last_insert_id(id) where                     status=0 limit 1”,然后用:"select last_insert_id()"就可以得到被修改数据的id;通过id查询到改数据返回给用户,这样我们就可以      不用锁机制来完成并发问题了!

    当然这只是代码层面问题,如果是因为服务器承受不住压力的话,我们应该考虑服务器架构方面的问题!

     

    展开全文
  • }, 3000) } </script> <body> ()">获取数据</button> <div> 内容都显示在这里</h1> <div id="tripe"></div> </div> </body> </html> 一个很简单的请求多个数据并发问题,这里需要处理的是,将请求到的数据按顺序...

    首先引用一下阮一峰大佬的一段话:

    Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

    来看一下简单的Promise示例:

    let promise = new Promise(function (resolve,reject) {
        console.log('Hello');
        resolve();
    });
    promise.then(function () {
        console.log('World!');
    });
    console.log('the');
    

    猜测一下输出顺序是什么,好像提供的输出样例已经暴露了:

    Hello
    the
    World!

    Promise对象新建后立即执行,所以最先输出Hello
    然后其状态由pending转化为resolved,输出the
    最后then作为指定回调函数,当前脚本所有同步任务执行完才会执行

    再来看一个实战演练:

    <html>
        <script>
            function getData(str) {
                return new Promise((resolve, reaject) => {
                    setTimeout(() => resolve(str), Math.random()*3000);
                })
            }
            function getAllData() {
                var all_str = '';
                getData('望').then(res => all_str += res);
                getData('子').then(res => all_str += res);
                getData('成').then(res => all_str += res);
                getData('龙').then(res => all_str += res);
                setTimeout(() => {
                    document.getElementById('tripe').innerHTML = all_str;
                }, 3000)
            }
        </script>
        <body>
            <button onclick="getAllData()">获取数据</button>
            <div>
                <h1>内容都显示在这里</h1>
                <div id="tripe"></div>
            </div>
        </body>
    </html>
    

    一个很简单的请求多个数据的并发问题,这里需要处理的是,将请求到的数据按顺序排列,组成“望子成龙”四字成语,但是由于设置的存储时间是随机的,导致每次生成的数据顺序都是打乱的,怎么解决这个问题呢?
    在这里插入图片描述

    1. 简单粗暴,直接把
    setTimeout(() => resolve(str), Math.random()*3000);
    

    改为

    setTimeout(() => resolve(str), 0);
    

    按照请求字符的顺序来,消除随机计时,确实,这种方法简单有效,但是为了练习Promise,所以使用第二种方法(别骂了,别骂了,水平有限,一时想不出来好的例子QAQ)
    2. 通过设置Promise对象来实现请求顺序的排列,代码如下:

    <html>
        <script>
            function getData(str) {
                return new Promise((resolve, reaject) => {
                    setTimeout(() => resolve(str), 0);
                })
            }
            function getAllData() {
                var all_str = '';
                getData('望').then(res => all_str += res);
                var promise = new Promise(function (resolve,reject) {
                    getData('子').then(res => all_str += res);
                    resolve();
                });
                promise.then(function () {
                    getData('龙').then(res => all_str += res);
                });
                getData('成').then(res => all_str += res);
                setTimeout(() => {
                    document.getElementById('tripe').innerHTML = all_str;
                }, 3000)
            }
        </script>
        <body>
            <button onclick="getAllData()">获取数据</button>
            <div>
                <h1>内容都显示在这里</h1>
                <div id="tripe"></div>
            </div>
        </body>
    </html>
    

    效果如下:
    在这里插入图片描述

    展开全文
  • 遇到的问题:多台应用部署调度任务,同时扫一张表的数据,并且对一张表的数据进行操作,可能出现的问题是一条数据被操作了两次,从而出现了并发性的问题解决办法:从sql上解决问题,并且限制查询条数。 例:...
  • 解决并发数据插入重复问题

    千次阅读 2019-05-07 10:25:23
    https://blog.csdn.net/nikobelic8/article/details/53308543
  • 解决并发数据重复

    千次阅读 2018-03-20 18:16:08
    此处就此问题的排查进行一个记录,希望可以给遇到了相同问题的朋友提供一个解决思路。 代码的业务逻辑如下图 问题定位 在网络拥堵、或者数据库反应迟缓、或是同时有多个请求同时请求时就会出现数据重复...
  • php解决并发问题

    万次阅读 2019-02-20 16:12:33
    我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台...
  • 一、海量数据解决方案 1、缓存 数据量很大最直接的解决方案就是使用缓存,缓存就是将数据库中获取的结果暂时保存起来,在下次使用时无需重新到数据库中获取,这样可以大大降低数据库的压力。 缓存的使用方式...
  • 1.出现问题在做数据库开发的时候,经常会遇到这样的一种情景:当一条数据不存在的时候,插入这条数据,如果这条数据的主键已经在数据库中存在,那么更新这条...问题:高并发的情况下数据会出问题,不能保证原子性...
  • 如何解决并发问题

    千次阅读 2018-08-15 15:11:24
    如何解决并发问题 转载:   一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构、性能的要求都很简单,...
  • 需求起因 在高并发的业务场景下,数据库...这个业务场景,主要是解决数据从Redis缓存,一般都是按照下图的流程来进行业务操作。 image 读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:...
  • 如何有效处理数据并发操作问题

    千次阅读 2019-06-17 10:13:23
    本篇文章以我在真实项目中遇到的数据并发问题作为背景,讲解问题出现的原因及解决的办法,以及从中得到的反思。并发中踩过很多坑,可能还有不足的地方,但会一直学习成长,现在将学习到的东西记录下来,,,,努力...
  • 在sqlserver里有个timestamp的字段类型,如果我们需要对某个数据表进行并发请求的处理,以防止出现两个用户同时修改表中同一条数据造成冲突的情况。这时可以给该表添加一个timestamp的字段,当添加、修改数据的时候...
  • 以及今天要谈到的Redis并发竞争问题,这里的并发指的是多个redis的client同时set key引起的并发问题。 比如:多客户端同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是由于并发设置的原因...
  • 一、网站应用背景开发一个网站的应用程序,当用户规模比较小的时候,使用简单的:一台应用服务器+一台数据库服务器+一台文件服务器,这样的话完全可以解决一部分问题,也可以通过堆硬件的方式来提高网站应用的访问...
  • 解决考试系统高并发数据加载不正确问题

    千次阅读 热门讨论 2015-06-30 15:47:10
    分享在解决考试系统高并发数据加载不稳定问题的经历,并提出一个疑问,希望大家帮助解答
  • 如何解决并发问题

    千次阅读 2018-05-25 10:44:06
    (对网上的答案进行了略微的整理,随后再进行优化,希望可以提取一些你需要的数据)一、如何解决并发1.尽量使用缓存,包括用户缓存,信息缓存等,多花点内存来做缓存,可以大量减少与数据库的交互,提高性能。2....
  • Java多线程:解决并发环境下数据插入重复问题

    万次阅读 热门讨论 2016-11-23 17:56:53
    一家文学网站向我系统推多线程低并发推送数据,我这边观察日志和数据库,发现有一个作者被存储了2次到数据库中。按照程序的编写逻辑,重复的数据是会被判断出来不被存储的。 2.原因分析 由于网络原因,客户可能...
  • 并发解决更新数据丢失问题

    千次阅读 2018-01-23 11:50:51
    https://segmentfault.com/a/1190000012939310
  • 一家文学网站向我系统推多线程低并发推送数据,我这边观察日志和数据库,发现有一个作者被存储了2次到数据库中。按照程序的编写逻辑,重复的数据是会被判断出来不被存储的。2.原因分析 由于网络原因,客户可能连续推...
  • MongoDB 3.0 的解决数据库并发问题

    千次阅读 2018-01-31 19:15:51
    在传统的关系型数据库(mysql或者 sql server)的事务中,通常在处理并发问题时借助于他们的特性: 1.原子性 2.一致性 3.隔离性 4.持久性 其中的每个特征的特性我就不特意进行解释了,大家可以自行百度 这里特别说的...
  • redis解决并发问题,如商品秒杀

    千次阅读 2019-05-16 10:01:50
    redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动。。。 redis之所以能解决并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,...
  • Redis incr解决并发问题

    万次阅读 2019-08-15 11:19:33
    2、每天的工单生成量是30W,所以会存在并发问题 解决思路: 1、首先乐观的认为redis不会宕机,对应的缓存不会被清除(除非人为操作,人为操作会有独立的补救办法) 2、将工单编码存到缓存中(redis),其值只存...
  • sqlite解决并发问题

    千次阅读 2017-05-11 15:20:44
    验证了sqlite脚本的并发性,弱爆了,一个脚本写,一个脚本查询,查询的脚本运行down掉了...使用linux系统中的消息队列完美解决,将写、查询操作都放到一个消息队列中,获取消息队列中数据进行先进先出的数据库读写操作

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 581,458
精华内容 232,583
关键字:

怎么解决数据并发问题