一、應用場景:
本文應用的場景為在查詢數據時,發現數據不存在此時就需要去查詢數據庫並且更新緩存,此時可能存在高並發的請求同時打在數據庫上,而針對這種情況必須要給這些請求加鎖,故而采用了分布式鎖的方式。(當然分布式鎖的應用場景較多,我只是針對本人工作的業務場景做了對應的處理)
二、Redis鎖的工具類:
/** * Redis分布式鎖 */ @Component public class RedisLock { @Autowired private RedisTemplate redisTemplate; /** * 加鎖 * @param key * @param value 當前時間+超時時間 * @return */ public boolean lock(String key, String value) { if(redisTemplate.opsForValue().setIfAbsent(key, value)) {//相當於SETNX,setIfAbsent方法設置了為true,沒有設置為false return true; } //假設currentValue=A 接下來並發進來的兩個線程的value都是B 其中一個線程拿到鎖,除非從始至終所有都是在並發(實際上這中情況是不存在的),只要開始時有數據有先后順序,則分布式鎖就不會出現“多賣”的現象 String currentValue = String.valueOf(redisTemplate.opsForValue().get(key)); //如果鎖過期 解決死鎖 if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) { //獲取上一個鎖的時間,鎖過期后,GETSET將原來的鎖替換成新鎖 String oldValue = String.valueOf(redisTemplate.opsForValue().getAndSet(key, value)); if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) { return true; } } return false;//拿到鎖的就有執行權力,拿不到的只有重新再來,重新再來只得是讓用戶手動繼續搶單 } /** * 解鎖 * @param key * @param value */ public void unlock(String key, String value) { try { String currentValue = String.valueOf(redisTemplate.opsForValue().get(key)); if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) { redisTemplate.opsForValue().getOperations().delete(key); } }catch (Exception e) { e.printStackTrace(); } } }
三、業務代碼:
JSONArray allApplyForms = null; if (this.redisActivityService.exists(RedisKeys.ApplyFormList_KEY+String.valueOf(activityId))) {
// 如果redis緩存在有該活動的作品列表,則直接從redis中獲取 Object allApplyFormsJSON = this.redisActivityService.get(RedisKeys.ApplyFormList_KEY+String.valueOf(activityId)); allApplyForms = JSONArray.fromObject(allApplyFormsJSON.toString());
} else { long time = System.currentTimeMillis()+(20 * 1000); if(!redisLock.lock("ApplyFormListLock", String.valueOf(time))){ return CORSUtil.getResult(0, "當前訪問量大,刷新一下", null, callback); } allApplyForms = this.activityService.getAllApplyForms(activityId, thumbnailWidth,thumbnailHeight, contentCode, formTitle, flag,formModel); if(allApplyForms ==null){ return CORSUtil.getResult(0, "沒有對應的參選表單", null,callback); } this.redisActivityService.set(RedisKeys.ApplyFormList_KEY+String.valueOf(activityId), allApplyForms.toString(),Long.parseLong(systemConfigService.getConfig("redisOverTime"))+new Random().nextInt(200)); redisLock.unlock("ApplyFormListLock", String.valueOf(time)); }
1,從redis中獲取對應的數據,如果獲取到直接返回,如果沒有就走接下來的加鎖代碼
2,如果加鎖不成功,則說明已經有請求進入到后面的業務邏輯,這時候就直接返回給客戶端,等待
3,如果加鎖成功,則查詢數據並更新Redis,最后再釋放鎖