在现如今电商盛行的时期,会出现很多促销活动,最为常见的就是秒杀。在秒杀系统中最为常见的问题就是会出现超卖的情况,那么如何来杜绝超卖的情形了,在业务逻辑层面可以使用缓存以及加锁的手法来避免超卖的情形。

  现如今nosql已经非常流行和稳定了,在此我将通过redis和php来说明如何实现锁机制。当然我使用redis加锁并不是我的秒杀系统,而是最近做的一个项目有个用户提现,初期没有考虑到会有人恶意刷新接口,而导致用户无限制提现。经过查看nginx日志,发现用户在同一时间段,通过刷接口的方法超额提现,导致亏损

  起初的提现代码如下:

$uid = $this->user_id;
if (empty($uid)) {
return $this->responseJson(300, '请先登录');
}
$money = $this->request->get('money', 'trim');
$formId = $this->request->get('formId', 'trim', '');
$user_model = new User();
$user_info = $user_model->getUserInfoById($uid);
$balance = $user_info['balance'] / 100;
$phone = $this->request->get('phone', 'trim');
if ($money < 1) {
return $this->responseJson(300, '最小提现金额为1元');
}
if ($balance < $money) {
return $this->responseJson(300, '账户余额不足');
}
$openid = $user_info['openId'];
if (in_array($openid, ['oMl_x0DiSCYqQuqJOKV9bAqR1Ugk', 'oMl_x0B1zoY70dbiwxAt4lg2fmL4', 'oMl_x0E0jYOK6NpbzwmTVJowpfpk', 'oMl_x0GHeAdKCZ8Iv1KD0CmdZLQ0', 'oMl_x0FBU1eWya1fG5xtVxryUYG4', 'oMl_x0CIMr5tItEy1QPtpI9eFJak', 'oMl_x0JWFdGOnf80W5oZOX-XfGcw'])) {
return $this->responseJson(300, '正在处理中');
}
if (!empty($phone)) {
if (!isset($user_info['phone']) || (isset($user_info['phone']) && $user_info['phone'] != $phone)) {
$user_model->update(['_id' => $this->user_id], ['$set' => ['phone' => $phone]]);
}
}
$order_id = \Common::getOrder();
$res = $user_model->updateBalanceById($uid, $money * 100);
if ($res) {
$trans_res = \GlobalFunc::transfer($uid, $order_id, $openid, 'NO_CHECK', $money); if ($trans_res === false) {
$user_model->updateBalanceById($uid, $money * 100, 2);
$redis->del('with:draw:' . $uid);
if (!empty($formId)) {
$TemplateMsg = new \TemplateMsg();
$to_user = $user_info['openId'];
$tem_id = 'ZSpYvjqdawADxr7j_8DJFuaoAdWbHhXdnAFlp5QF9L0';
$data = array(
'keyword1' => array('value' => '提现', 'color' => "#173177"),
'keyword2' => array('value' => date('Y-m-d H:i:s'), 'color' => "#173177"),
'keyword3' => array('value' => $trans_res['err_code_des'] . "。申请提现金额已自动退回账户余额中。", 'color' => '#173177'),
);
$page = 'pages/balance/balance';
$TemplateMsg->doSend($to_user, $tem_id, $formId, $data, $page);
}
return $this->responseJson(300, $trans_res['err_code_des']);
}
$redis->del('with:draw:' . $uid);
} else {
$redis->del('with:draw:' . $uid);
return $this->responseJson(300, '提现失败,请稍后重试');
}
....

看上面代码逻辑感觉似乎没有什么问题,确实,在正常的情况下是不会出现问题,如果有人恶意的去刷接口的话,上述问题就出现了。为了防止用户恶意刷接口,所以对现有代码做了如下修改

$uid = $this->user_id;
if (empty($uid)) {
return $this->responseJson(300, '请先登录');
}
$money = $this->request->get('money', 'trim');
$formId = $this->request->get('formId', 'trim', '');
$user_model = new User();
$user_info = $user_model->getUserInfoById($uid);
$balance = $user_info['balance'] / 100;
$phone = $this->request->get('phone', 'trim');
if ($money < 1) {
return $this->responseJson(300, '最小提现金额为1元');
}
if ($balance < $money) {
return $this->responseJson(300, '账户余额不足');
}
$redis = $this->cache('redis');
$redis->incr('with:draw:' . $uid);
if (intval($redis->get('with:draw:' . $uid)) > 1) {
return $this->responseJson(300, '正在处理中');
}
$openid = $user_info['openId'];
if (in_array($openid, ['oMl_x0DiSCYqQuqJOKV9bAqR1Ugk', 'oMl_x0B1zoY70dbiwxAt4lg2fmL4', 'oMl_x0E0jYOK6NpbzwmTVJowpfpk', 'oMl_x0GHeAdKCZ8Iv1KD0CmdZLQ0', 'oMl_x0FBU1eWya1fG5xtVxryUYG4', 'oMl_x0CIMr5tItEy1QPtpI9eFJak', 'oMl_x0JWFdGOnf80W5oZOX-XfGcw'])) {
return $this->responseJson(300, '正在处理中');
}
if (!empty($phone)) {
if (!isset($user_info['phone']) || (isset($user_info['phone']) && $user_info['phone'] != $phone)) {
$user_model->update(['_id' => $this->user_id], ['$set' => ['phone' => $phone]]);
}
}
$order_id = \Common::getOrder();
$res = $user_model->updateBalanceById($uid, $money * 100);
if ($res) {
$trans_res = \GlobalFunc::transfer($uid, $order_id, $openid, 'NO_CHECK', $money); if ($trans_res === false) {
$user_model->updateBalanceById($uid, $money * 100, 2);
$redis->del('with:draw:' . $uid);
if (!empty($formId)) {
$TemplateMsg = new \TemplateMsg();
$to_user = $user_info['openId'];
$tem_id = 'ZSpYvjqdawADxr7j_8DJFuaoAdWbHhXdnAFlp5QF9L0';
$data = array(
'keyword1' => array('value' => '提现', 'color' => "#173177"),
'keyword2' => array('value' => date('Y-m-d H:i:s'), 'color' => "#173177"),
'keyword3' => array('value' => $trans_res['err_code_des'] . "。申请提现金额已自动退回账户余额中。", 'color' => '#173177'),
);
$page = 'pages/balance/balance';
$TemplateMsg->doSend($to_user, $tem_id, $formId, $data, $page);
}
return $this->responseJson(300, $trans_res['err_code_des']);
}
$redis->del('with:draw:' . $uid);
} else {
$redis->del('with:draw:' . $uid);
return $this->responseJson(300, '提现失败,请稍后重试');
}
...

  代码调整后似乎防止了用户刷接口的行为,但是后期有用户反映,自己提不了现了。经过一番查看,原来redis的值一直存在,虽然用户操作完成后会删除key,但是也会存在在用户没有完全操作完成而导致流程中断,所以会导致key删除失败,为了解决锁不释放的问题,又对上述代码进行修改,在设置锁的时候,设置一个过期时间,修复如下

if (intval($redis->get('with:draw:' . $uid)) > 1) {
if ($redis->ttl('with:draw:' . $uid) == -1) {
$redis->expire('with:draw:' . $uid, 30);
}
return $this->responseJson(300, '正在处理中');
}

  这样就可以实现锁不释放的问题,但是上述代码除了使用incr操作外,还可以使用redis的setnx来代替,其实是一样的效果,但是无论你用那种还是有点问题就是,当你写入成功之后,突然断网或服务器宕机的情况,这时还会出现上述问题,那应该如何来解决呢。其实完全可以通过redis的 Multi/Exec结合来解决上述问题,其代码如下

$redis->multi();
$redis->setNX($key, $value);
$redis->expire($key, $ttl);
$redis->exec();

  这样就可以解决突然情况带来的妖怪问题了

  总结:通过redis可以实现大并发的数据请求操作,通过事务的操作来加锁和释放锁,达到数据完整性

  虽然上述问题解决了,但是代码还是有待优化,从 2.6.12 起,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。所以上述代码可以简化为

  

$redis->set($key, $random, array('nx', 'ex' => $ttl));

以上就是如何解决并发和恶意刷接口的解决方法,以作记录

php在大并发下redis锁实现的更多相关文章

  1. 跟着大神学zookeeper分布式锁实现-----来自Ruthless

    前几天分享了@Ruthless大神的Redis锁,发现和大家都学习了很多东西.因为分布式锁里面,最好的实现是zookeeper的分布式锁.所以在这里把实现方式和大家分享一下. zookeeper分布式 ...

  2. PHP使用redis防止大并发下二次写入(如如何防止重复下订单)

    php调用redis进去读写操作,大并发下会出现:读取key1,没有内容则写入内容,但是大并发下会出现同时多个php进程写入的情况,这个时候需要加一个锁,即获取锁的php进程有权限写. $lock_k ...

  3. PHP使用redis防止大并发下二次写入

    php调用redis进去读写操作,大并发下会出现:读取key1,没有内容则写入内容,但是大并发下会出现同时多个php进程写入的情况,这个时候需要加一个锁,即获取锁的php进程有权限写. $lock_k ...

  4. 解锁redis锁的正确姿势

    解锁redis锁的正确姿势 redis是php的好朋友,在php写业务过程中,有时候会使用到锁的概念,同时只能有一个人可以操作某个行为.这个时候我们就要用到锁.锁的方式有好几种,php不能在内存中用锁 ...

  5. redis锁机制介绍与实例

    转自:https://m.jb51.net/article/154421.htm 今天小编就为大家分享一篇关于redis锁机制介绍与实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要 ...

  6. PHP Redis锁

    一.什么是 Redis Redis是由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库 二.什么是 Redis 分布式锁 分布式锁其实可以理解为:控 ...

  7. 【原创】(求锤得锤的故事)Redis锁从面试连环炮聊到神仙打架。

    这是why技术的第38篇原创文章 又到了一周一次的分享时间啦,老规矩,还是先荒腔走板的聊聊生活. 有上面的图是读大学的时候,一次自行车骑行途中队友抓拍的我的照片.拍照的地方,名字叫做牛背山,一个名字很 ...

  8. (实例篇)php 使用redis锁限制并发访问类示例

    1.并发访问限制问题 对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功. 例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制 ...

  9. php 使用redis锁限制并发访问类

    1.并发访问限制问题 对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功. 例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制 ...

  10. .net的页面在大并发下出现503错误

    .net的页面在大并发下偶尔出现503错误 我们开发了一个回调页面,由一个工具负责调用,由于压力非常大,回调页面通过6台服务器负载均衡的: 最近业务系统又再次扩容,回调页面压力成倍增加,在高峰时间段偶 ...

随机推荐

  1. LeetCode40.组合总和II

    LeetCode40.组合总和II 力扣题目链接(opens new window) 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 ...

  2. 基于donetcore/CAP实现分布式事务一致性

    官网:https://cap.dotnetcore.xyz 相关介绍 CAP 是一个EventBus,同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架.它有助于创建可扩展,可靠并且易于 ...

  3. kafka消费者处理能力低引起rebalance分析

    一.背景介绍 项目上进行算法调度的需求,打算采用kafka作为消息中间件,通过将多个算法消费者加入到同一个group中并行的处理算法请求,从而达到高效处理的目的.但是算法处理的时间较长,多则几十分钟, ...

  4. CANopen学习笔记(二)通讯对象PDO和SDO等

    通讯对象 PDO 我的观点:一个 CANopen 设备可以拥有最多 512 个 RPDO 和 512 个 TPDO,总共最多 1024 个 PDO.(得到GPT4o的肯定) CiA协议栈观点:一个只有 ...

  5. Ubuntu 安装 libwebkitgtk-1.0-0

    在 Ubuntu 上安装完 PDI 后启动 spoon.sh 时提示安装 libwebkitgtk-1.0-0.由于 apt 官方源中不包含此软件包,因此要添加该软件包的源,以及源对应的 gpg 公钥 ...

  6. 【YashanDB数据库】YAS-02032 column type is incompatible with referenced column type

    [标题]错误码处理 [问题分类]外键约束创建报错 [关键字]YAS-02032 [问题描述]设置外键约束报错,数据元数据不正确. [问题原因分析]外键字段类型不支持,比如varchar2(64) ,指 ...

  7. 熔断、限流、降级 —— SpringCloud Hystrix

    概述 Hystrix 为 微服务架构提供了一整套服务隔离.服务熔断和服务降级的解决方案.它是熔断器的一种实现,主要用于解决微服务架构的高可用及服务雪崩等问题 Hystrix 的特性如下: 服务熔断:H ...

  8. C++11 线程同步接口std::condition_variable和std::future的简单使用

    std::condition_variable 条件变量std::condition_variable有wait和notify接口用于线程间的同步.如下图所示,Thread 2阻塞在wait接口,Th ...

  9. Flutter 因你更优秀 | 2021 第一季度开发者调研

    Flutter 终于在新的一年迎来了 2.0 版本,这是继 Dart 健全的空安全 Beta 版,以及 在测试方面取得重大进展 之后的一个全新的里程碑.在迈入这个新版本的当下,我们也已经准备好为大家带 ...

  10. 一些OI常用小技巧啊

    1.卡常 \[---总有人以为自己比编译器聪明 \;\;\;by\;\;bezel \] 我们可能确实没有编译器聪明,但是,为了防止CCF的老人机出现什么问题,卡一卡常还是有必要的. 如果实在被逼无奈 ...