经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
php和redis实现秒杀活动的流程
来源:jb51  时间:2019/7/18 8:36:58  对本文有异议

1 说明

前段时间面试的时候,一直被问到如何设计一个秒杀活动,但是无奈没有此方面的实际经验,所以只好凭着自己的理解和一些资料去设计这么一个程序

主要利用到了redis的string和set,string主要是利用它的k-v结构去对库存进行处理,也可以用list的数据结构来处理商品的库存,set则用来确保用户进行重复的提交

其中我们最主要解决的问题是

-防止并发产生超抢/超卖

2 流程设计

3 代码

3.1 服务端代码

  1. class MiaoSha{
  2.  
  3. const MSG_REPEAT_USER = '请勿重复参与';
  4. const MSG_EMPTY_STOCK = '库存不足';
  5. const MSG_KEY_NOT_EXIST = 'key不存在';
  6.  
  7. const IP_POOL = 'ip_pool';
  8. const USER_POOL = 'user_pool';
  9.  
  10. /** @var Redis */
  11. public $redis;
  12. public $key;
  13.  
  14. public function __construct($key = '')
  15. {
  16. $this->checkKey($key);
  17. $this->redis = new Redis(); //todo 连接池
  18. $this->redis->connect('127.0.0.1');
  19. }
  20.  
  21. public function checkKey($key = '')
  22. {
  23. if(!$key) {
  24. throw new Exception(self::MSG_KEY_NOT_EXIST);
  25. } else {
  26. $this->key = $key;
  27. }
  28. }
  29.  
  30. public function setStock($value = 0)
  31. {
  32. if($this->redis->exists($this->key) == 0) {
  33. $this->redis->set($this->key,$value);
  34. }
  35. }
  36.  
  37. public function checkIp($ip = 0)
  38. {
  39. $sKey = $this->key . self::IP_POOL;
  40. if(!$ip || $this->redis->sIsMember($sKey,$ip)) {
  41. throw new Exception(self::MSG_REPEAT_USER);
  42. }
  43. }
  44.  
  45. public function checkUser($user = 0)
  46. {
  47. $sKey = $this->key . self::USER_POOL;
  48. if(!$user || $this->redis->sIsMember($sKey,$user)) {
  49. throw new Exception(self::MSG_REPEAT_USER);
  50. }
  51. }
  52.  
  53. public function checkStock($user = 0, $ip = 0)
  54. {
  55. $num = $this->redis->decr($this->key);
  56. if($num < 0 ) {
  57. throw new Exception(self::MSG_EMPTY_STOCK);
  58. } else {
  59. $this->redis->sAdd($this->key . self::USER_POOL, $user);
  60. $this->redis->sAdd($this->key . self::IP_POOL, $ip);
  61. //todo add to mysql
  62. echo 'success' . PHP_EOL;
  63. error_log('success' . $user . PHP_EOL,3,'/var/www/html/demo/log/debug.log');
  64. }
  65. }
  66.  
  67. /**
  68. * @note:此种做法不能防止并发
  69. * @func checkStockFail
  70. * @param int $user
  71. * @param int $ip
  72. * @throws Exception
  73. */
  74. public function checkStockFail($user = 0,$ip = 0) {
  75. $num = $this->redis->get($this->key);
  76. if($num > 0 ){
  77. $this->redis->sAdd($this->key . self::USER_POOL, $user);
  78. $this->redis->sAdd($this->key . self::IP_POOL, $ip);
  79. //todo add to mysql
  80. echo 'success' . PHP_EOL;
  81. error_log('success' . $user . PHP_EOL,3,'/var/www/html/demo/log/debug.log');
  82. $num--;
  83. $this->redis->set($this->key,$num);
  84. } else {
  85. throw new Exception(self::MSG_EMPTY_STOCK);
  86. }
  87. }
  88. }
  89.  

3.2 客户端测试代码

  1. function test()
  2. {
  3. try{
  4. $key = 'cup_';
  5. $handler = new MiaoSha($key);
  6. $handler->setStock(10);
  7. $user = rand(1,10000);
  8. $ip = $user;
  9. $handler->checkIp($ip);
  10. $handler->checkUser($user);
  11. $handler->checkStock($user,$ip);
  12. } catch (\Exception $e) {
  13. echo $e->getMessage() . PHP_EOL;
  14. error_log('fail' . $e->getMessage() .PHP_EOL,3,'/var/www/html/demo/log/debug.log');
  15. }
  16. }
  17.  
  18. function test2()
  19. {
  20. try{
  21. $key = 'cup_';
  22. $handler = new MiaoSha($key);
  23. $handler->setStock(10);
  24. $user = rand(1,10000);
  25. $ip = $user;
  26. $handler->checkIp($ip);
  27. $handler->checkUser($user);
  28. $handler->checkStockFail($user,$ip); //不能防止并发的
  29. } catch (\Exception $e) {
  30. echo $e->getMessage() . PHP_EOL;
  31. error_log('fail' . $e->getMessage() .PHP_EOL,3,'/var/www/html/demo/log/debug.log');
  32. }
  33. }

4 测试

测试环境说明

  • ubantu16.04
  • redis2.8.4
  • php5.5

在服务端代码里面我们有两个函数分别是checkStock和checkStockFail,其中checkStockFail不能在高并发的情况下效果很差,不能在redis层面保证库存为0的时候终止操作。

我们利用ab工具进行测试

其中 www.hello.com 是配置的虚拟主机名称 flash-sale.php 是我们脚本的名称

  1. #第1种情况 500并发下 用客户端的test2()去执行
  2. ab -n 500 -c 100 www.hello.com/flash-sale.php

log日志的记录结果:

  1. #第2种情况 5000并发下 用客户端的test2()去执行
  2. ab -n 5000 -c 1000 www.hello.com/flash-sale.php

log日志的记录结果:

  1. #第3种情况 500并发下 用客户端的test()去执行
  2. ab -n 500 -c 100 www.hello.com/flash-sale.php

log日志的记录结果:

  1. #第4种情况 5000并发下 用客户端的test()去执行
  2. ab -n 5000 -c 1000 www.hello.com/flash-sale.php

log日志的记录结果:

5 总结

我们从日志中可以很明显的看出第3、4中情况下,可以保证商品的数量总是我们设置的库存值10,但是在情况1、2下,则产生了超卖的现象

redis来控制并发主要是利用了其api都是原子性操作的优势,从checkStock和checkStockFail中可以看出,一个是直接decr对库存进行减一操作,所以不存在并发的情况,但是另一个方法是将库存值先取出做减一操作然后再重新赋值,这样的话,在并发下,多个进程会读取到多个库存为1的值,因此会产生超卖的情况

以上所述是小编给大家介绍的php和redis实现秒杀活动的流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对w3xue网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号