lnmp高并发情况下出现TP5中的函数重复执行

Codekeyth
  • 8

1.

问题描述

lnmp高并发情况下出现TP5中的函数重复执行

问题出现的环境背景及自己尝试过哪些方法

已经使用cache作为判断标准,限制一段时间内就算函数执行,也保证数据不会入库。

相关代码

php代码:

 public function winprize()
{
    if (Request::instance()->isPost()){
        $openid=Cookie::get('hzz_openid');
        $win_type=db('winprize')->where('openid',$openid)->where('state',0)->field('type')->find();
        if($win_type){
            if($win_type['type']==0){
                return json(['code'=>4,'msg'=>'存在记录']);
            }else{
                return json(['code'=>5,'msg'=>'不存在的记录']);
            }
        }
        $user_id=input('id');
        $user=db('user')->where('id',$user_id)->find();
        if($user['number']<$user['max_number']){
            if($user['max_number']>2){
                $fact_assist=db('assist')->where('openid_ass',$openid)->count();
                if($fact_assist<(5*$user['max_number'])){
              db('assist')->where('openid_ass',$openid)->update(['max_number'=>floor($fact_assist/5)]);
                    return json(['code'=>2,'msg'=>'无效的']);
                }
            }
            $jackpot=db('jackpot')->where('state',1)->find();
            $data['jackpot_id']=$jackpot['id'];
            $data['user_id']=$user['id'];
            $data['openid']=$user['openid'];
            $data['create_time']=time();
            if($jackpot['type']==0){
                if(!empty($user['redpack_openid'])){
                    $data['redpack_openid']=$user['redpack_openid'];}
                $data['price']=$jackpot['price'];
            }else{
                $data['type']=$jackpot['type'];}
            Db::startTrans();
            try{
                db('jackpot')->where('id',$jackpot['id'])->update(['state'=>0]);
                db('user')->where('openid',$openid)->update(['number'=>$user['number']+1]);
                db('winprize')->insert($data);
                Db::commit();
                return json(['code'=>0,'type'=>$jackpot['type']]);
            } catch (\Exception $e) {

                Db::rollback();
                return json(['code'=>3,'msg'=>'事务出现异常!']);}
        }else{
            return json(['code'=>1,'msg'=>'没有抽奖机会']);}
    }
    return json(['code'=>-1,'msg'=>'错误']);
}

nginx.conf:

user www www;

worker_processes 16;

error_log /home/wwwlogs/nginx_error.log crit;

pid /usr/local/nginx/logs/nginx.pid;
google_perftools_profiles /tmp/tcmalloc;

events

{
    use epoll;
    worker_connections 65535;
    multi_accept on;
}

http

{
    include       mime.types;
    default_type  application/octet-stream;

    server_names_hash_bucket_size 128;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    client_max_body_size 50m;

    sendfile   on;
    tcp_nopush on;

    keepalive_timeout 60;

    tcp_nodelay on;

    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 256k;

    gzip on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 2;
    gzip_types     text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss;
    gzip_vary on;
    gzip_proxied   expired no-cache no-store private auth;
    gzip_disable   "MSIE [1-6]\.";

    #limit_conn_zone $binary_remote_addr zone=perip:10m;
    ##If enable limit_conn_zone,add "limit_conn perip 10;" to server section.

    server_tokens off;
    access_log off;

server

{
    listen 80 default_server;
    #listen [::]:80 default_server ipv6only=on;
    server_name _;
    index index.html index.htm index.php;
    root  /home/wwwroot/default;

    #error_page   404   /404.html;

    # Deny access to PHP files in specific directory
    #location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; }

    include enable-php.conf;

    location /nginx_status
    {
        stub_status on;
        access_log   off;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
    }

    location ~ .*\.(js|css)?$
    {
        expires      12h;
    }

    location ~ /.well-known {
        allow all;
    }

    location ~ /\.
    {
        deny all;
    }

    access_log  /home/wwwlogs/access.log;
}

include vhost/*.conf;
}

查阅了一下nginx与php错误日志,没有发现有异常记录。请大神帮忙分析一下

回复
阅读 2.3k
3 个回答

使用redis的分布式锁就可以解决了,缓存解决不了问题
同一时刻取请求缓存都会查不到缓存,导致并发

redis set命令可以传入NXEX来解决并发问题

可以从两方面来解决:
1、前端页面,如果是按钮提交的话,需要做一些防刷操作,提交一次就不能再提交了,防止多次刷提交。
2、后端,对方法可以考虑加锁来解决,一般PHP的话,可以考虑使用文件加锁, 如果用到了Redis的话,
可以用Redis分布式锁来解决。超时时间可以稍微设置5秒左右。
3、对于数据库方面来说,如果你的数据要唯一,那么你的字段和主键设计时候要设置确保唯一性。(唯一索引)。

文件锁的方式解决:

//优化方案4:使用非阻塞的文件排他锁
$fp = fopen("lock.txt", "w+");
 if(!flock($fp,LOCK_EX | LOCK_NB)){
     echo "系统繁忙,请稍后再试";
     return;
 }
//执行业务逻辑
flock($fp,LOCK_UN);//释放锁
fclose($fp);

关于分布式锁的解决方案,可以参考:
基于 redis 的 setnx 来解决这一问题。
https://github.com/ronnylt/re...

wemk
  • 951

更新语句条件不够,比如说将status由0改成1,where里面要带上当前status的值做判断,确保只执行一次。
db('jackpot')->where(['id'=>$jackpot['id'],'state'=>0])->update(['state'=>1]);

只要state不为0的,更新都会失败,事务就会回滚

宣传栏