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