一、問題描述:
單應用切換至分布式,優惠劵被同一時間同一優惠劵領取多張,比如使用模擬器1s內請求1000次,可能被領取100張。
以前插入前先查詢是否存在,無法有效解決,還是會被擼羊毛。
二、解決方法:
1.app前端增加控制,(比如按鈕點擊后失效);
2.利用數據庫層的事務處理,比如:插入的同時查詢是否存在,利用數據庫自身的事務管理,同一sql實現
insert into buyer_collect(buyer_id,goods_id,create_time)
select #{userId},#{goodsId},now()
from dual where not exists(select 1 from buyer_collect where buyer_id = #{userId} and goods_id = #{goodsId})
3.redis互斥鎖(個人推薦,復用性好,穩定性好)
如果框架還未整合redis請自行查閱處理,這邊直接上解決方案:引入jar包,本人使用2.9
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
利用jedis類的set方法特性
nxxx的值只能取NX或者XX,如果取NX,則只有當key不存在是才進行set,如果取XX,則只有當key已經存在時才進行set
expx的值只能取EX或者PX,代表數據過期時間的單位,EX代表秒,PX代表毫秒。
time 過期時間,單位是expx所代表的單位。
public String set(String key, String value, String nxxx, String expx, int time) { this.checkIsInMultiOrPipeline(); this.client.set(key, value, nxxx, expx, time); return this.client.getStatusCodeReply(); }
編寫公共方法,可根據自己的實際情況自行改造,本用例來源於網絡,較為成熟穩定(親測可行)
1 private static final String LOCK_SUCCESS = "OK"; 2 private static final String SET_IF_NOT_EXIST = "NX"; 3 private static final String SET_WITH_EXPIRE_TIME = "EX"; 4 5 /** 6 * 獲取分布式鎖 7 * @param lockKey 鎖 8 * @param requestId 請求標識 9 * @param expireTime 超期時間 10 * @return 是否獲取成功 11 */ 12 public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) { 13 Jedis jedis = null; 14 try { 15 jedis = getJedis();
16 String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 17 if (LOCK_SUCCESS.equals(result)) { 18 return true; 19 } 20 return false; 21 } finally { 22 returnResource(jedis); 23 } 24 }
//Redis緩存 5S內重復請求無效
Boolean result = null;
String key = "項目名稱+功能模塊" + userId + couponId;
String requestId = UUID.randomUUID().toString();
try {
//獲取分布式鎖
result = redisCacheService.tryGetDistributedLock(key, requestId, 5);
}catch (Exception e){
return new JsonResult(JsonResultCode.FAILURE,"連接redis取數異常","");
}
if(!result){
return new JsonResult(JsonResultCode.FAILURE,"請勿頻繁點擊!","");
}
附上使用的一小段代碼,希望能秒懂解決實際問題
userId :用戶id
couponId :優惠劵id
項目名稱+功能模塊:根據自己項目命名
結合自己項目框架自行修改,一次編寫,復用性好。有效解決項目中類型的問題
