PHP 使用 redis 进行商品秒杀设计思路

1 篇文章 0 订阅

前期准备

  • 背景

    相信很多在小公司打拼的小伙伴
    对于秒杀系统真的是可遇不可求
    我们只能通过模拟演练

    一方面熟悉高并发场景、提升编码技能
    另一方面,为进入大厂做好准备
    此处,我主要还是阐述下设计思路
    有不同见解,欢迎指摘 …

  • 模拟环境

    PHP7.2、CentOS7.9、Redis6.0.8、ab 压测工具

☛ 设计思路

  • 首先,要明确的一点是,不能直接按照传统商品订单思路处理,毕竟大流量下不能丢失用户美好的交互性
    然后,准备秒杀服务器,不影响主业务运行
    用户在秒杀等待页面,使用 ajax 异步更新倒计时

  • 点击"抢购"触发时
    使用 Redis 开启事务
    提取用户唯一标识 ID,首先集中到 redis 的一个商品数量的集合"kill_user_que"
    然后,将符合要求的 用户ID ,存入秒杀队列"kill_user"
    注意商品数量的递减变化
    最终的结果是得到一个,不会超售商品数量的 秒杀队列(kill_user)

  • 设置一个或多个线程,也可以是定时任务
    去秒杀队列(kill_user) 中提取 用户ID,依次执行下单逻辑

具体的业务处理,要根据实际场景,再做代码优化 … 推荐文章 —— 【用 Redis 轻松实现秒杀系统】


测试参考

☛ 秒杀处理代码参考

假定要抢购的商品数量为 100 件,即 "kill_num" 要提前设置为 100

public function testFastSale(){
       $redis2 = new \Redis();
       $redis2->connect('192.168.80.224',6379);

       $killNumSet = 100;
       //初始化设置秒杀商品数量
       //$redis2->set('kill_num',$killNumSet);
       
       //模拟发起请求的用户ID
       $userID = rand(1111,2222);

       $killNum = $redis2->get('kill_num');
       if ($killNum > 0){
           //TODO 此时,还有商品可进行抢购
           if ($redis2->sIsMember('kill_user_que',$userID)){
               //TODO 此时说明用户已经抢到了
               $message = 'Sorry,一个账号只能抢一件!';
           }else{
               $countCanBuyer = $redis2->sCard('kill_user_que');
               if ($countCanBuyer >= $killNumSet){
                   $message = 'Sorry,当前排队已满员!';
               }else{
                   $redis2->watch('kill_num','kill_user','kill_user_que');
                   $redis2->multi(); //开启事务
                   $redis2->sAdd('kill_user_que',$userID); //加入集合
                   $redis2->decr('kill_num'); //商品数量减一
                   $redis2->rPush('kill_user',$userID);//将用户有序的压入队列
                   $redis2->exec(); //执行事务
                   $message = "恭喜,抢购成功!";
               }
           }
       }else{
           $message = "Sorry,商品已售完!";
       }
       return $message;
   }

【提示】:

  • 为了避免同一用户多抢商品,我使用的是集合 "kill_user_que"
  • 而对符合抢购的用户ID,使用队列 "kill_user" 进行存储 (有序性)
    方便后期,对队列的弹出操作(POP),后续下单业务处理

建议,参考文档 —— Redis 事务|【菜鸟教程】

☛ ab 模拟高并发请求

  • 在此,我使用 ab 压力测试工具,模拟高并发的请求场景
    运行命令如下:"ab -c 50 -n 3000 http://tp5pro.com/index/test"
  • 执行完成后,可在 redis 中查看数据("kill_num"、"kill_user"):

后续逻辑,就要见仁见智了 …

附录

总觉得写得太少,那就多做一点补充吧!

▶ 为什么我设置一个集合、一个队列?

  • 一开始,我只是想到使用一个队列 "kill_user"就好
    但是,我发现:
    如果同一个用户账号,可能不止一次能抢到商品
    这在正常业务中,一般是不允许的

  • 为了保证用户的唯一性,想到了改用集合
    但是,集合有个缺点:不能记录顺序
    为了业务合理性
    先排到的人自然会先下订单
    虽然,也存在有序集合(sorted set)可以满足需求
    但是,分析后发现会有很多关于排序取值的逻辑处理,很是繁杂

  • 此时想到
    首先,使用一个集合"kill_user_que",负责前期对 用户ID 的筛选,保证唯一性
    然后,将刷选符合要求的 用户ID 压入一个队列"kill_user"
    后期,只需操作队列 "kill_user" 中的数据即可

如此一来,就出现了这种情况,当然这里主要是提供一种解决思路 !

▶ 可不是仅仅有了代码就行了!

对于秒杀类的需求,需要考虑的方面会比较多,可不只有编码

  • 一般来说
    秒杀最容易引来用户流量(小项目没有客户群,那就么啥讨论性了)
    可能要考虑 Redis 集群的部署、负载均衡、带宽等支持
    其次,还有前端页面静态化、ajax 等代码的设限
    以及流量削峰、限流等操作
    最后还要有薅羊毛等恶意刷单的考量、人工最终审核等等 …

  • 个人拙见,谢谢 …

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是Python使用MongoDB和Redis实现商品秒杀的示例代码。在这个示例中,我们使用Python的pymongo库和redis库来操作MongoDB和Redis。 首先,需要安装pymongo和redis库: ``` pip install pymongo redis ``` 然后,连接MongoDB和Redis: ```python import pymongo import redis # 连接MongoDB mongo_client = pymongo.MongoClient('mongodb://localhost:27017/') db = mongo_client['seckill'] # 连接Redis redis_client = redis.Redis(host='localhost', port=6379, db=0) ``` 接下来,定义商品和订单的数据结构,并初始化商品库存和秒杀开始时间: ```python # 商品数据结构 product = { 'id': 1, 'name': 'iPhone 12', 'price': 5999, 'stock': 10 } # 订单数据结构 order = { 'id': 1, 'user_id': 1, 'product_id': 1, 'quantity': 1, 'total_price': 0 } # 初始化商品库存和秒杀开始时间 redis_client.hset('seckill', product['id'], f"{product['stock']},{time.time()}") ``` 接下来,实现秒杀功能。在实现秒杀功能之前,需要先判断商品库存是否充足,如果充足,则减少库存并生成订单: ```python # 秒杀函数 def seckill(user_id, product_id, quantity): # 获取商品库存和秒杀开始时间 stock, start_time = map(int, redis_client.hget('seckill', product_id).split(',')) # 判断商品库存是否充足 if stock < quantity: return False # 开始秒杀 with redis_client.pipeline() as pipe: while True: try: # 监视商品库存 pipe.watch('seckill') # 判断商品库存是否充足 stock, start_time = map(int, pipe.hget('seckill', product_id).split(',')) if stock < quantity: pipe.unwatch() return False # 开始事务 pipe.multi() # 减少商品库存 pipe.hincrby('seckill', product_id, -quantity) # 生成订单 order['id'] = db.counters.find_one_and_update({'_id': 'order_id'}, {'$inc': {'seq': 1}})['seq'] order['user_id'] = user_id order['product_id'] = product_id order['quantity'] = quantity order['total_price'] = quantity * product['price'] db.orders.insert_one(order) # 提交事务 pipe.execute() return True except redis.exceptions.WatchError: # 事务失败,重试 continue ``` 最后,测试秒杀功能: ```python # 测试秒杀功能 seckill(1, product['id'], 1) ``` 以上代码是Python使用MongoDB和Redis实现商品秒杀的示例,具体实现需要根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值