PHP菜鸟博客_共同学习分享PHP技术心得【PHP爱好者】
thinkphp5中基于redis的分布式锁实现方案
2024-9-20 菜鸟站长


1.首先先封装一个原生redis客户端的类








<?php


//Cache类的set方法并不直接支持Redis的SET命令的所有选项,如NX和EX。因此,我们需要使用更底层的Redis客户端来实现这一点。


namespace app\common\lib;




class RedisDistributedLock


{


    private $client;


    private $key;


    private $timeout; // 锁的超时时间,单位秒,结合场景而定


    private $isWaiting; // 是否等待,默认是0,不等待


    private $retryInterval; // 重试间隔,单位毫秒,默认1000是1秒,一般默认1000就行,整好是1秒




    public function __construct($key, $timeout = 30, $isWaiting = 0, $retryInterval = 1000)


    {


        $this->key = $key;


        $this->timeout = $timeout;


        $this->isWaiting = $isWaiting;


        $this->retryInterval = $retryInterval;


        $redis = new \Redis();


        try {


            $redis->connect(config("session")['host'], config("session")['port']);


            if (!empty(config("session")['password'])) {


                $redis->auth(config("session")['password']); // 密码验证


            }


            $this->client = $redis;


        } catch (\Exception $e) {


            throw new \Exception("Redis 连接或认证失败: " . $e->getMessage());


        }


    }




    public function acquire()


    {


        if ((bool)$this->isWaiting) {


            $startTime = microtime(true);


            do {


                if ($this->tryAcquire()) {


                    return true;


                }


                usleep($this->retryInterval * 1000); // 等待一段时间再试


            } while (microtime(true) - $startTime < $this->timeout);


            throw new \Exception('无法获取锁');


        } else {


            return $this->tryAcquire();


        }


    }




    public function release()


    {


        try {


            return $this->client->del($this->key);


        } catch (\Exception $e) {


            throw new \Exception("释放锁失败: " . $e->getMessage());


        }


    }




    private function tryAcquire()


    {


        try {


            return (bool)$this->client->set($this->key, 1, ['nx', 'ex' => $this->timeout]);


        } catch (\Exception $e) {


            throw new \Exception("尝试获取锁失败: " . $e->getMessage());


        }


    }




    public function getTtl()


    {


        try {


            $ttl = $this->client->ttl($this->key);


            if ($ttl === -1) {


                throw new \Exception("锁不存在或没有设置过期时间");


            }


            return $ttl;


        } catch (\Exception $e) {


            throw new \Exception("获取锁的剩余生存时间失败: " . $e->getMessage());


        }


    }




}









2.测试的demo方法








<?php


namespace app\index\controller;




use think\Controller;


use think\Db;


use think\Request;


use think\Session;


use think\Cache;


use \think\Env;


use lizengbang\Shorturl;


use  think\Image;


use GuzzleHttp\Client;


use Psr\Http\Message\ResponseInterface;


use GuzzleHttp\Exception\RequestException;


use think\Response;




use think\Queue;




use app\middleware\LimitRequestByAppid;


use app\common\lib\RedisDistributedLock;




class TestController extends Controller


{




    


    public function redis_lock2()


    {


        // redis锁,避免短时间重复点击导出


        $lock_key = 'dao_chu_order_son_lock2';


        $lock_ttl = 10; // 锁的超时时间(秒)


        // 记录开始时间


        $start_time = microtime(true);




        // 创建锁对象


        $lock = new  RedisDistributedLock($lock_key, $lock_ttl);


        // 尝试获取锁,这里有阻塞吗


        $acquired = $lock->acquire();


        var_dump($acquired);




        if (!$acquired) {


            error_tips('导出操作正在进行中,请稍后再试!');


            die;


        }




        try {


            echo "我是业务内容";


            sleep(4);


        } finally {


            // 记录结束时间


            $end_time = microtime(true);


            $execution_time = $end_time - $start_time;




            // 释放锁


            $deleted = $lock->release();


            var_dump($deleted); // 检查锁是否被成功删除




            // 输出执行时间


            echo "执行时间: " . $execution_time . " 秒";


        }


    }







    public function redis_lock22()


    {


        // redis锁,避免短时间重复点击导出


        $lock_key = 'dao_chu_order_son_lock2';


        $lock_ttl = 10; // 锁的超时时间(秒)


        // 记录开始时间


        $start_time = microtime(true);




        // 创建锁对象


        $lock = new  RedisDistributedLock($lock_key, $lock_ttl);


        // 尝试获取锁,这里有阻塞吗


        $acquired = $lock->acquire();


        var_dump($acquired);




        if (!$acquired) {


            error_tips('导出操作正在进行中,请稍后再试!');


            die;


        }




        try {


            echo "我是业务内容";


        } finally {


            // 记录结束时间


            $end_time = microtime(true);


            $execution_time = $end_time - $start_time;




            // 释放锁


            $deleted = $lock->release();


            var_dump($deleted); // 检查锁是否被成功删除




            // 输出执行时间


            echo "执行时间: " . $execution_time . " 秒";


        }


    }




}









3.在项目中实际使用的案例








 //导出订单操作方法1




    public function ajax_do_daochu_order1()


    {


        //权限校验, 第二个参数是否ajax, 1是ajax,0不是ajax


        $basekeynum = session('cn_accountinfo.basekeynum');


        check_auth(request()->controller() . '/orderlist_shenhe', 1);


        $request = Request::instance();


        $param = $request->param();


        $clientkeynum = $basekeynum;




        // redis锁,避免短时间重复点击导出,锁一定要靠上,从获取数据之前就锁住,要不然可能还重复


        $lock_key = $request->action() . '_' . $clientkeynum;


        $lock_ttl = 60; // 锁的超时时间(秒)


        $lock = new  RedisDistributedLock($lock_key, $lock_ttl);


        // 尝试获取锁,这里有阻塞吗


        $acquired = $lock->acquire();


        if (!$acquired) {


            error_tips("导出操作正在进行中,请稍" . $lock->getTtl() . "秒后再试!");


            die;


        }


        //开启事务


        $trans_result = true;


        Db::startTrans();


        try {


            //sleep(3);




            $keynum = isset($param['keynum']) == true ? $param['keynum'] : '-1';


            $field = isset($param['field']) ? $param['field'] : '';


            $keyword = isset($param['keyword']) ? $param['keyword'] : '';


            $between_time = isset($param['between_time']) ? $param['between_time'] : '';


            $orderby = isset($param['orderby']) ? $param['orderby'] : 'desc';


            $is_print = isset($param['is_print']) ? $param['is_print'] : '-1';


            $where = "1=1  and  clientkeynum='$basekeynum'  and  order_status='1' and is_daochu='0'   and (channels ='' or (channels !='' and third_sync_sta='1'))";


            //订单状态


            if ($keynum != ''  && $keynum != '-1') {


                $where .= " and merchantkeynum='$keynum' ";


                //订单状态


            }




            if ($field == 'order_sn') {


                $where .= " and order_sn  like '%$keyword%' ";


            } elseif ($field == 'goodname') {


                $where .= " and goodname  like '%$keyword%' ";


            } elseif ($field == 'phone') {


                $where .= " and phone  like '%$keyword%' ";


            } elseif ($field == 'name') {


                $where .= " and name  like '%$keyword%' ";


            }




            if ($between_time != '') {


                $time_arr = explode('~', $between_time);


                $start_time = strtotime($time_arr[0]);


                $end_time = strtotime($time_arr[1]);


                $where .= " and add_time > $start_time and add_time < $end_time";


            }


            //是否打印如果是-1,则不过滤


            if ($is_print != '-1') {


                $where .= " and is_print  ='$is_print'  ";


            }




            $arr_data = Db::table('client_order_info')->where($where)->where("pay_status='1'")->order('add_time', $orderby)->limit($offset . ',' . $pagesize)->select();




            if (empty($arr_data)) {


                throw new \Exception('没有选中数据');


                die;


            }


            $order_ids = '';


            $daochu[] = array('订单唯一标识', '收货人姓名', '收货人手机号', '收货人地址', '商品编号', '商品名称',  '订单号', '订单状态', '快递名称', '快递单号', '其他运单号', '运费', '商品价格', '订单金额', '下单时间', '客户备注', '客服备注', '支付方式', '在线支付金额', '余额支付金额', '支付时间', '是否vip订单');


            foreach ($arr_data as $key => $value) {


                $order_ids .= $value['order_id'] . ',';


                foreach ($value as $k => $v) {


                    switch ($k) {


                        case 'keynum':


                            $r[0] = $value['keynum'];


                            break;


                        case 'consignee':


                            $r[1] = $value['consignee'];


                            break;


                        case 'phone':


                            $r[2] = $value['phone'];


                            break;


                        case 'detail_address':


                            $r[3] = $value['province'] . $value['city'] . $value['area'] . $value['town'] . $value['detail_address'];


                            break;


                        case 'goodssku':


                            $r[4] = $value['goodssku'];


                            break;


                        case 'goodsinfo':


                            $r[5] = $value['goodsinfo'];


                            break;


                        case 'order_sn':


                            $r[6] = $value['order_sn'];


                            break;


                        case 'order_status':


                            $r[7] = get_order_status($value['order_id']);


                            break;


                        case 'shipping_name':


                            $r[8] = $value['shipping_name'];


                            break;


                        case 'shipping_num':


                            $r[9] = $value['shipping_num'];


                            break;


                        case 'shipping_othernum':


                            $r[10] = $value['shipping_othernum'];


                            break;




                        case 'shipping_fee':


                            $r[11] = $value['shipping_fee'];


                            break;


                        case 'goods_total_money':


                            $r[12] = $value['goods_total_money'];


                            break;


                        case 'order_total_money':


                            $r[13] = $value['order_total_money'];


                            break;


                        case 'add_time':


                            $r[14] = date_format_tpl($value['add_time']);


                            break;


                        case 'remark':


                            $r[15] = $value['remark'];


                            break;


                        case 'admin_remark':


                            $r[16] = $value['admin_remark'];


                            break;


                        case 'pay_method':


                            $r[17] =  get_paymethod_names($value['keynum']);;


                            break;


                        case 'online_money':


                            $r[18] = $value['online_money'];


                            break;


                        case 'member_money':


                            $r[19] = $value['member_money'];


                            break;


                        case 'pay_time':


                            $r[20] = date_format_tpl($value['pay_time']);;


                            break;


                        case 'is_vip':


                            $r[21] = get_order_vip_name($value["is_vip"], "excel");


                            break;


                        default:


                            break;


                    }


                }


                ksort($r);


                $daochu[] = $r;


            }




            $daochu = array_filter($daochu);


            $order_ids = substr($order_ids, 0, strlen($order_ids) - 1);


            $date = date('Y-m-d', time());


            $xls_name = $date;


            $binfo = Db::table('client_order_batch_chu')->where('date', $date)->where('clientkeynum', $basekeynum)->order('id desc')->find();


            if ($binfo) {


                $pcname_arr = explode('-', $binfo['name']);


                $pcnum = $pcname_arr[3] + 1;


                $pcname = $date . '-' . $pcnum;


            } else {


                $pcname = $date . '-1';


            }




            $batch = [


                'clientkeynum' => $clientkeynum,


                'order_id' => $order_ids,


                'addtime' => time(),


                'action_user' => session('cn_accountinfo.accountname'),


                'name' => $pcname,


                'date' => date('Y-m-d', time()),


            ];




            //没有要下载的数据


            if ($order_ids == '') {


                throw new \Exception('没有选中数据');


                die;


            }




            foreach ($arr_data as $k => $v) {


                //订单操作日志


                $log['order_sn'] = $v['order_sn'];


                $log['clientkeynum'] = $clientkeynum;


                $log['action_user'] = session('cn_accountinfo.accountname');;


                $log['action_note'] = '勾选导出订单';


                $log['add_time'] = time();


                $log_flag = Db::table('client_order_log')->insert($log);


                if (!$log_flag) {


                    throw new \Exception('insert,client_order_log失败!');


                }


            }




            //把这些订单改成已导出状态


            $order_up = Db::table('client_order_info')->where("order_id in ($order_ids)")->update(['is_daochu' => 1, 'daochu_time' => time()]);


            if (!$order_up) {


                throw new \Exception('update,client_order_info失败!');


            }




            $insert = Db::table('client_order_batch_chu')->insert($batch);


            if (!$insert) {


                throw new \Exception('insert,client_order_batch_chu失败!');


            }




            // 提交事务


            Db::commit();


        } catch (\Exception $e) {


            // 回滚事务


            Db::rollback();


            $msg = $e->getMessage();


            $trans_result = false;


        } finally {


            //无论是否正常或抛出异常,finally 块中的代码都会执行,注意try里面要尽量包含查询,然后就是里面不要有die,一定要抛出异常否则锁不能自动删除。


            // 释放锁


            $deleted = $lock->release();


        }


        if (!$trans_result) {


            error_tips($msg);


            die;


        }




        addoperatelog('批量导出订单', json_encode($param, JSON_UNESCAPED_UNICODE));


        create_xls($daochu, $pcname);


    }









4.下面这种thinkphp5封装的不可行





    //Cache类的set方法并不直接支持Redis的SET命令的所有选项,如NX和EX。因此,我们需要使用更底层的Redis客户端来实现这一点。


    public function redis_lock1()


    {


        // redis锁,避免短时间重复点击导出


        $lock_key = 'dao_chu_order_son_lock1';


        $lock_ttl = 10; // 锁的超时时间(秒)


        // 记录开始时间


        $start_time = microtime(true);


        $lock_info = Cache::store('redis')->get($lock_key);


        var_dump($lock_info);




        // 尝试获取锁,不支持NX和EX,每次都是返回true,所以没得办法,只能用原生的redis客户端


        $acquired = Cache::store('redis')->set($lock_key, 1, ['nx', 'ex' => $lock_ttl]);


        var_dump($acquired);


        if (!$acquired) {


            error_tips('导出操作正在进行中,请稍后再试!');


            die;


        }


        try {


            echo "我是业务内容";


            sleep(4);


        } finally {


            // 记录结束时间


            $end_time = microtime(true);


            $execution_time = $end_time - $start_time;




            // 释放锁


            $deleted = Cache::store('redis')->rm($lock_key);


            var_dump($deleted); // 检查锁是否被成功删除




            // 输出执行时间


            echo "执行时间: " . $execution_time . " 秒";


        }


    }




    //Cache类的set方法并不直接支持Redis的SET命令的所有选项,如NX和EX。因此,我们需要使用更底层的Redis客户端来实现这一点。


    public function redis_lock11()


    {


        // redis锁,避免短时间重复点击导出


        $lock_key = 'dao_chu_order_son_lock1';


        $lock_ttl = 10; // 锁的超时时间(秒)


        // 记录开始时间


        $start_time = microtime(true);


        $lock_info = Cache::store('redis')->get($lock_key);


        var_dump($lock_info);




        // 尝试获取锁,不支持NX和EX,每次都是返回true,所以没得办法,只能用原生的redis客户端


        $acquired = Cache::store('redis')->set($lock_key, 1, ['nx', 'ex' => $lock_ttl]);


        var_dump($acquired);


        if (!$acquired) {


            error_tips('导出操作正在进行中,请稍后再试!');


            die;


        }


        try {


            echo "我是业务内容";


            sleep(4);


        } finally {


            // 记录结束时间


            $end_time = microtime(true);


            $execution_time = $end_time - $start_time;




            // 释放锁


            $deleted = Cache::store('redis')->rm($lock_key);


            var_dump($deleted); // 检查锁是否被成功删除




            // 输出执行时间


            echo "执行时间: " . $execution_time . " 秒";


        }


    }




发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容