公司前段时间根据业务方的需要需要做一个抢红包的活动,在网上查了很多资料。记录整体设计思路和运行过程中出现的各种问题。产品要求:1、红包支持配置开始时间、结束时间、类型(随机数额或固定数额)、单次最小红包数额、单次最大红包数额2、领取红包的业务条件(根据业务information,specifiedcertainconditionspeoplecangrab)设计思路:难点一:红包算法(根据红包配置的最大、最小金额、数量生成一组符合条件的红包)因为红包有最大和最小金额配置为单个红包,所以不能完全使用随机分配。因此,要求是:*单个红包的金额必须大于最小金额,但也必须小于最大金额*根据红包的总金额和数量,必须准确分红*单个红包精确到分,即小数点后两位数。实现代码:/**@todo设置随机红包数量*返回数组*/publicfunctionsetRandMoney(){$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){//在Averageonlinemoneyreduction$temp=$this->min+$this->xRandom($this->min,$average);$结果[$i]=$温度;$this->total-=$temp;}else{//将钱添加到平均值$temp=$this->max-$this->xRandom($average,$this->max);$结果[$i]=$温度;$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->总计--;}}}//如果钱是负数,就得从生成的小红包里取出while($this->total<0){for($i=0;$i<$this->num;$i++){如果($this->total<0&&$result[$i]>$this->min){$result[$i]--;$this->总计++;}}}if(!empty($result)){//将红包放入队列foreach($resultas$val){$this->redis->lPush($this->redpack_money_queue.$this->act_id,$val/100);}返回['代码'=>'0','味精'=>'成功'];}return['code'=>'1','msg'=>'创建红包失败,请检查参数'];}/***生成一个介于min和max之间的随机数,但是概率不是平均的,概率从min到max逐渐增加*先平方,然后生成平方值范围内的随机数,再平方根,所以产生了先“胀”再“缩”的效果。*/私有函数xRandom($bonus_min,$bonus_max){$sqr=intval($this->sqr($bonus_max-$bonus_min));$rand_num=rand(0,($sqr-1));返回intval(sqrt($rand_num));}privatefunctionsqr($n){return$n*$n;}由于取最小和最大金额之间的随机数时使用了intval()函数,该算法只能处理整数,因此处理时先将金额乘以100,入队时再除以100末尾,使其精确到小数点后两位。难点二:高并发时对服务器的访问压力,比如抢红包、抢购1元、闪购等业务场景,都同时向服务器累积大量请求,造成服务器紧张资源,程序无法处理。那么我们要做的就是控制流量,防止大量的请求通过web服务器直接到数据库层。那么从用户访问url到接收到返回结果的整个过程是怎样的呢?客户端层,用户在微信中打开网址,DNS解析域名到服务器web服务器层,Apache、Nginx或Tomcat等服务器层,分配php-fpm进程,代码接收参数进行逻辑处理和数据持久化级别,并将结果保存到Mysql或Redis级别客户端级优化方案:(限流)前端URL使用html静态页面显示内容,并尽可能压缩页面显示的图片以减少服务器带宽压力。推荐使用base64解码图片,使用连接池控制流量。当用户点击抢红包时,发起ajax请求,调用后台java编写的redisincr接口。每次调用key值为+1,返回自增id。当后台代码处理完后,减去它的key值,因为incr自动增加到原子级别,所以前端可以根据当前有多少用户在等待。根据自身服务器配置和业务场景,估计超过N个请求就会导致服务器出现问题。如果当前等待处理的请求数大于N,则前端提示用户“请求过多,请稍后再试”,否则,可以正常发起请求。Web层优化方案(lua+nginx实现频率控制)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代码层(防止发送过多,重复接收,权限等)使用redisqueue队列功能控制超发情况,lpush每个计算好的小红包入队列,每次收到请求后消费最后一个小红包,因为redis队列是阻塞模式,所以当队列为空时,不会返回数据,这样就可以保证在有并发的情况下不会给多人发红包。使用redislist集合来控制重复集合的情况。每次接收到请求后,将用户id放入已经接收到的集合中(这个很重要,一定要放在消费队列之前的集合中,否则并发接收会出现重复),如果消费成功则跳出,否则从接收集合中移除。因为业务需求处理起来非常繁琐,所以在创建活动的时候,根据活动规则把可以领取的人放到集合中,权限判断可以由要领取的集合来控制。下面是我的代码实现(小菜,请勿喷)/**@todo获取红包金额*@returnarray*/publicfunctiondoRush(){$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.'元'];}数据层(使用异步持久化)用户接收成功后,将用户id和接收到的金额存储到receivedredis队列中,异步进程根据user_id和money值更新自己的数据到mysql表--------------------------------------------------我是万恶的分界线---------------------------------------------------------------补充说明:这是我第一次把实际的开发过程和思路写出来。作为小菜对我来说已经很不错了。接触,在网上参考了很多资料,学到了很多东西。如果大家有更好的解决方案,请多多交流~~----一个PHP小菜------
