搖一搖抽獎

情景分析:

       商家時常會搞一些抽獎活動,這類活動有個特點就是抽獎用戶會在抽獎時間突然大量的湧入系統,這時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();
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章