公司前段时间根据业务方需求需要做一个抢红包的活动,网上也搜索了很多资料。记录下整体的设计思路以及运营过程中的各种问题。

产品需求:

1.红包支持配置开始时间、结束时间、类型(随机金额或固定金额)、单个最小红包金额、单个最大红包金额

2.可领取红包的业务条件(根据业务信息指定某些满足条件的人可以抢)

设计思路:

难点1:红包算法(根据红包配置最大、最小金额、数量生成符合条件的红包集合)

因为红包有配置单个红包的最大和最小金额,所以不能完全使用随机分配的方式。

所以要求:

* 单个红包金额既要大于最小金额,又要小于最大金额
    * 根据红包总金额和个数要正好将钱分完

* 单个红包精确到分,也就是小数点后两位

实现代码:

    /** @todo 设置随机红包金额* return array*/public function setRandMoney(){$result = [];//取小数点后两位将金额乘100$this->total = $this->total * 100;//红包总金额$this->min = $this->min * 100;//单个红包最小金额$this->max = $this->max * 100;//单个红包最大金额//获取红包平均金额$average = $this->total / $this->num;for ($i = 0; $i < $this->num; $i++) {//因为小红包的数量通常是要比大红包的数量要多的,因为这里的概率要调换过来。//当随机数>平均值,则产生小红包//当随机数<平均值,则产生大红包if (rand($this->min, $this->max) > $average) {// 在平均线上减钱$temp = $this->min + $this->xRandom($this->min, $average);$result[$i] = $temp;$this->total -= $temp;} else {// 在平均线上加钱$temp = $this->max - $this->xRandom($average, $this->max);$result[$i] = $temp;$this->total -= $temp;}}// 如果还有余钱,则尝试加到小红包里,如果加不进去,则尝试下一个。while ($this->total > 0) {for ($i = 0; $i < $this->num; $i++) {if ($this->total > 0 && $result[$i] < $this->max) {$result[$i]++;$this->total--;}}}// 如果钱是负数了,还得从已生成的小红包中抽取回来while ($this->total < 0) {for ($i = 0; $i < $this->num; $i++) {if ($this->total < 0 && $result[$i] > $this->min) {$result[$i]--;$this->total++;}}}if (!empty($result)) {//将红包放入队列之中foreach ($result as $val) {$this->redis->lPush($this->redpack_money_queue . $this->act_id, $val / 100);}return ['code' => '0', 'msg' => 'success'];}return ['code' => '1', 'msg' => '创建红包失败,请检查参数'];}/*** 生产min和max之间的随机数,但是概率不是平均的,从min到max方向概率逐渐加大。* 先平方,然后产生一个平方值范围内的随机数,再开方,这样就产生了一种“膨胀”再“收缩”的效果。*/private function xRandom($bonus_min, $bonus_max){$sqr = intval($this->sqr($bonus_max - $bonus_min));$rand_num = rand(0, ($sqr - 1));return intval(sqrt($rand_num));}private function sqr($n){return $n * $n;}

因为取最小和最大金额之间随机数的时候使用了intval()函数导致该算法只能处理整数,故在处理的时候将金额乘100 ,在最后入队列的时候再将其 除100,这样就将其精确到小数点后两位。

难点2:高并发时对服务器的访问压力
类似抢红包、1元抢购,秒杀等业务场景都是在同一时间大量请求堆积到服务器,从而导致服务器资源紧张,程序处理不过来。那么我们要做的就是将流量控制住,不让大量的请求透过web服务器直接打到数据库层。那么从用户访问url到收到返回结果整体流程是什么样子呢?

  • 客户端层,用户在微信中打开URL,DNS解析域名至服务器
  • web服务器层, Apache、Nginx或Tomcat等
  • 服务器层,分配php-fpm进程,代码接收参数进行逻辑处理
  • 数据持续化层次,将结果保存至mysql或Redis层次

客户端层优化方案:(限流)

  1. 前端URL使用html静态页面显示内容,并将页面显示图片尽量压缩,减少服务器带宽压力。推荐使用base64解码图片
  2. 使用连接池控制流量,用户点击抢红包时,发起ajax请求,调用后台使用java写的redis incr 接口,每次调用则键值 +1,并将自增id返回,当后台代码处理完后再将其键值减掉,因为incr自增为原子级别,所以前端可以根据当前有多少用户在等待中。 根据自身服务器配置以及业务场景预估N多请求会导致服务器出现问题,如果当前等待处理的请求数大于N则前端提示用户 "当前请求过多,请稍后再试",反之则可以正常发起请求。

Web层优化方案(lua+nginx实现频率控制)

  1. Nginx来处理访问控制的方法有多种,实现的效果也有多种,访问IP段,访问内容限制,访问频率限制等。

用Nginx+Lua+Redis来做访问限制主要是考虑到高并发环境下快速访问控制的需求。

Nginx处理请求的过程一共划分为11个阶段,分别是:

`post-read、server-rewrite、find-config、rewrite、post-rewrite、 preaccess、access、post-access、try-  files、content、log`.在openresty中,可以找到:`set_by_lua`,`access_by_lua`,`content_by_lua`,`rewrite_by_lua`等方法。那么访问控制应该是,`access`阶段。

2.根据请求的ip段来控制访问流量,每次接收到抢红包的url后将redis连接池中id自增,当超过某个峰值时跳转到等待页。
具体配置方案参考:http://homeway.me/2015/08/11/...

php代码层(防止出现发多、重复领取、权限等情况)

  1. 使用redis queue 队列功能来控制超发的情况,将每个算出来的小红包lpush至队列中,每次收到请求后消费最后一个小红包,因为redis的的队列为阻塞模式,所以当队列中为空时是不返回数据的,也就可以保证出现并发时不会一个红包分配给多人。
  2. 使用 redis list集合来控制重复领取的情况,每次接收到请求后将用户id放置已领取的集合中(这点很重要,一定要在消费队列前放置集合中,要不会出现因为并发导致重复领取),消费成功则跳出,反之则将其移出已领取集合。
  3. 因为业务需求处理起来很繁琐,所以在活动创建的时候就根据活动规则将可领取的人员放置集合中,权限判断可以使用待领取集合来控制。

以下为我的代码实现(小菜一枚,大神勿喷)

   /** @todo 获取红包金额* @return array*/public function doRush(){$act_info = $this->getPackInfo($this->act_id);if(empty($act_info)){return ['code'=>'1','msg'=>'活动信息错误,请联系管理员'];}if($act_info['start_time'] > now()){return ['code'=>'2','msg'=>"红包尚未开抢,请稍后再试"];}if($act_info['end_time'] <= now()){return ['code'=>'1','msg'=>'活动已结束'];}//将请求用户先放置已领取的集合中if(!$this->redis->sAdd($this->rushed_list_key,$this->user_id)){return ['code'=>'1','msg'=>'每个红包只能领取一次哦'];}$money = $this->redis->lPop($this->redpack_money_queue);if(empty($money)){$this->redis->sRem($this->rushed_list_key,$this->user_id);return ['code'=>'1','msg'=>'您来完了呦,红包已抢光'];}//将已抢的用户和金额记录至队列中$add_res = $this->amountAdd($money);if($add_res['code'] != 0){return ['code'=>'1','msg'=>'系统繁忙,请稍后再试'];}return ['code'=>'0','msg'=>'success','data'=>$money.'元'];}

数据层(使用异步持续化)

  1. 用户领取成功后,将用户id及领取的金额存至已领取的redis queue中,异步进程根据其中的user_id和money值将其数据更新至mysql表中

--------------------------------------------------我是万恶的分割线------------------------------------------------------------------

补充说明:
本人第一次将实际开发过程以及想法落实到书面上,对于我这种小菜来说已经很不错了,恳求各位大神勿喷。其中红包算法和一些处理方案也是第一次接触,参考了网上很多资料,学到了很多。如果你有更好的方案的话多多交流~~

                                                                                                                                ----PHP小菜一枚------

高并发红包整体设计方案相关推荐

  1. springboot实现高并发红包系统(java 全网最全包括语音口令 文字口令 普通 拼手气)

    博主技术笔记 博主开源微服架构前后端分离技术博客项目源码地址,欢迎各位star springboot实现高并发红包系统(全网最全) 下面的业务处理请根据你们实际的场景进行处理 1.sql设计 CREA ...

  2. 高并发资金交易系统设计方案—百亿双十一、微信红包背后的技术架构

    21CTO社区导读 : 今天带来的是一个长篇文章.主要讲解高可用的互联网交易系统架构,包括双十一.支付宝&微博红包技术架构,以及微信红包的技术架构,希望能给各位提供价值. 概述 话说每逢双十一 ...

  3. 互联网产品之百万级高并发技术整体架构

    高并发是由于移动APP或网站PV(page view)即页面浏览量或点击量大,单台服务器无法承载大量访问所带来的压力,因此会采用服务器集群技术,用N台服务器进行分流,对于每次访问采取负载均衡策略,被分 ...

  4. 高并发高可用处理大数据量

    教学大纲: 教学内容 大型互联网三大问题-高并发,高可用,大数据量 第一天内容如下: 1:什么是高并发? 2:为什么要解决高并发 3:画图分析:1) 多用户访问单台App服务器及数据库时,性能分析,瓶 ...

  5. 面试官绝杀:系统是如何支撑高并发的?

    作者 | 中华石杉 责编 | 伍杏玲 很多人面试的时候被问到一个让人特别手足无措的问题:你的系统如何支撑高并发? 大多数同学被问到这个问题压根儿没什么思路去回答,不知道从什么地方说起,其实本质就是没经 ...

  6. 面试官,再也别问我的系统如何支持高并发了

    1.一道面试题的背景引入 这篇文章,我们聊聊大量同学问我的一个问题,面试的时候被问到一个让人特别手足无措的问题:你的系统如何支撑高并发? 大多数同学被问到这个问题压根儿没什么思路去回答,不知道从什么地 ...

  7. 怎么检测并发处理能力达每秒2万次_系统如何支撑高并发

    目录 概述 最简单的系统架构 负载均衡+分库分表 + 读写分离系统架构 负载均衡+分库分表 + 读写分离+缓存集群系统架构 负载均衡+分库分表 + 读写分离+缓存集群+消息中间件集群系统架构 php7 ...

  8. 500并发 一台服务器的性能_面试官绝杀:系统是如何支撑高并发的?

    作者 | 中华石杉 责编 | 伍杏玲 本文经授权转载石杉的架构笔记(ID:shishan100) 很多人面试的时候被问到一个让人特别手足无措的问题:你的系统如何支撑高并发? 大多数同学被问到这个问题压 ...

  9. redis 缓存数据_Redis 缓存数据方案对比:常规 VS 高并发服务器

    1 Redis 是什么 Redis 是一种开源的非关系型数据库.起源于负载较大时,当前关系型数据库无法承载的情况. 到目前为止,Redis 可以用作数据库.缓存.消息处理.Redis 可以存储键和5种 ...

  10. 面试官问我有没有高并发架构经验,我慌的一批…

    目录 一.1 道面试题的背景引入 二.先考虑一个最简单的系统架构 三.系统集群化部署 四.数据库分库分表 + 读写分离 五.缓存集群引入 六.引入消息中间件集群 七.现在能hold住高并发面试题了吗 ...

最新文章

  1. java 中 bean 的生命周期
  2. Conda创建环境失败:CondaHTTPError: HTTP 000 CONNECTION FAILED
  3. js原生方法传参的细节(面试必问)
  4. nanodet学习笔记 tensorrt
  5. 解决LiteIDE 中的error process failed to start.
  6. 在本地库不连接远远程库的情况下操作远程库-----sql server
  7. IPMI远程管理一点记录
  8. 0.5px边框,css及sass
  9. 关于二分查找 使用 lower_bound
  10. 无法读取项目文件 .csproj
  11. and5.1PowerManagerService深入分析(四)PMS与Display模块
  12. 面试题——内网相关(一)
  13. 计算机网络协议各协议的作用,计算机网络通信协议有哪些?作用是什么?
  14. Vue项目实现web端飘窗
  15. PCB封装欣赏了解之旅(上篇)—— 常用元器件
  16. 20155313 杨瀚 《网络对抗技术》实验五 MSF基础应用
  17. 用html创建数独,数独做不出来怎么办
  18. 电源纹波怎么测量,纹波和噪声的区别
  19. .考试倒计时43天!来提分啦!
  20. 超详细,Python当中的pip常用命令大全

热门文章

  1. 过去的七天我是这样过的
  2. Docker 多模块打包启动(kitty-generator)
  3. HTML 5 span 标签
  4. OO CSS的个人理解
  5. Failed reading log event, reconnecting to retry
  6. linux下libnet编程 亲自测试可用
  7. 【Wonder整理】防止重复提交并弹出半透明对话框
  8. SAMBA服务和FTP/sshd 服务讲解
  9. JSJ——主数据类型和引用
  10. 分布式存储系统设计的关键问题