摇一摇抽奖

情景分析:

       商家时常会搞一些抽奖活动,这类活动有个特点就是抽奖用户会在抽奖时间突然大量的涌入系统,这时DB瞬间承受压力倍增,随时可能出现宕机的情况,从而影响整个业务。

需求分析:

       这类活动通常有以下几个需求:

  • 同一用户最多只能抽到一个奖品;
  • 若有多轮抽奖,上轮中奖的用户不能再次中奖;

优化思路:

  • 既然瓶颈很大一部分是DB导致的,那我们就想办法把请求拦截在上游,尽量减少对DB的读写操作,比如奖品有10个,那只能有10个人中奖,其他人的请求可以直接拦截在业务层,这里采用请求队列来解决,队列大小设置为奖品的数量10,当队列里的请求任务超过10时,直接将后续的抽奖请求返回不中奖;
  • 抽奖业务可以采用先到先得的设计思路,即谁的请求先发送过来谁中奖,直到奖品抽完,这样就尽快在短时间内结束抽奖,以减轻对服务器的压力;
  • 防止用户连续多次抽奖,若是点击按钮抽奖,可点击一次后按钮置灰,若是摇一摇抽奖,可设置抽奖一次需摇动的时间或者次数多一些;
  • 还可采用Nginx来负载均衡,Redis来缓存等;

代码参考:

  • 摇一摇抽奖页面:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>摇一摇</title>
<script src="/script/jquery.min.js"></script>
<script type="text/javascript">
    //需要判断浏览器是否支持  
    if (window.DeviceMotionEvent) {  
    	window.addEventListener('devicemotion', deviceMotionHandler, false);    
    } else{  
    	alert("不支持摇一摇功能"); 
    }  
    var shake_threshold = 2000;//摇动的阀值  
    var lastUpdate = 0;//变量保存上次更新的时间  
    var x, y, z, last_x, last_y, last_z;// x、y、z记录三个轴的数据以及上一次出发的时间  
    var count = 0;//计数器  
    var time1; 
    var time2;
    function deviceMotionHandler(eventData) {  
	    if(count==1){
	   	time1 = new Date().getTime();
	    }
	    var acceleration = eventData.accelerationIncludingGravity;//获取含重力的加速度  
	    var curTime = new Date().getTime();//获取当前时间  
	    var diffTime = curTime -lastUpdate;//时间差  
	    //固定时间段  
	   if (diffTime > 100) {  
		lastUpdate = curTime;  
		x = acceleration.x;  
		y = acceleration.y;  
		z = acceleration.z;  
		var speed = Math.abs(x+y+z-last_x-last_y-last_z) / diffTime * 10000;//速度  
		//在阀值内  
	        if(speed > shake_threshold){  
	            count++;  
	            //摇动5次都在阀值内开始抽奖
	            if(count==5){
	          	time2 = new Date().getTime();
			$("#form").submit();
	          	return;
	            }
		}           
	    	last_x = x;  
	        last_y = y;  
	        last_z = z;  
	    }  
    }   
</script> 
<style type="text/css">
.yaodong{
    z-index:8;
    animation:swing 1s ease 1s 1 both;
    -webkit-animation:swing 1s ease 1s infinite both;
    -ms-animation:swing 1s ease 1s infinite both;
    -o-animation:swing 1s ease 1s infinite both;
    -moz-animation:swing 1s ease 1s infinite both;				 
}
@-webkit-keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}@keyframes swing{20%{-webkit-transform:rotate3d(0,0,1,15deg);transform:rotate3d(0,0,1,15deg)}40%{-webkit-transform:rotate3d(0,0,1,-10deg);transform:rotate3d(0,0,1,-10deg)}60%{-webkit-transform:rotate3d(0,0,1,5deg);transform:rotate3d(0,0,1,5deg)}80%{-webkit-transform:rotate3d(0,0,1,-5deg);transform:rotate3d(0,0,1,-5deg)}100%{-webkit-transform:rotate3d(0,0,1,0deg);transform:rotate3d(0,0,1,0deg)}}.swing{-webkit-transform-origin:top center;-ms-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}
</style>
</head>
<body>
	<form id="form" action="yao_result" method="post">
		<input type="hidden" id="flag" name="flag" th:value="${flag}">
		<input type="hidden" id="yhbh" name="yhbh" th:value="${session.yhbh}">
	</form>
   	<div style="width:80%;margin:0 auto;margin-top: 25%;">
    	<img class="yaodong" src="img/sj.png" width="100%"/>
    </div>
</body>
</html>
  • 后台抽奖逻辑
import java.util.concurrent.BlockingQueue;

import javax.servlet.http.HttpServletRequest;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import cn.com.app.util.ArrayBlockingQueue;

public class Test {

	//假设奖品只有10个,则设置队列大小为10
	private static final Integer maxQueueSize = 10;
        //这里只做入队操作,ArrayBlockingQueue效率比较快,若需要同时做出队操作,则LinkedBlockingQueue效率比较快
	private static BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(10);
	
       /**
	* 摇一摇
	*/
	@RequestMapping("/yao")
	public String yao(Model model) throws Exception {
	    return "yao";
	}
    
      /**
	* 摇奖结果
	* @param yhbh:用户唯一编号
	*/
	@RequestMapping("/yao_result")
	public String yaoResult(Model model,HttpServletRequest request,String yhbh) throws Exception {
	    String result = "no_win";//未中奖页面
	    //检测当前的队列大小
            if (blockingQueue.size() < maxQueueSize) {
        	//已中奖的用户不可再次中奖
        	if(!blockingQueue.contains(yhbh) && blockingQueue.offer(yhbh)){
            	    /**
            	     * 这里入库中奖记录
            	     */
            	    result = "one_win";//中奖页面
                }
            }
	    return result;
	}
	
}
  • 由于队列中不能包含重复的用户,这里对ArrayBlockingQueue类的入队方法offer源码进行修改,可另保存为工具类:
    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
            	//添加不重复的元素
            	if(contains(e)){
            	    return false;
            	}else{
            	    enqueue(e);
                    return true;
            	}
            }
        } finally {
            lock.unlock();
        }
    }
发布了95 篇原创文章 · 获赞 131 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/rexueqingchun/article/details/99453000