福建泉州網(wǎng)站建設(shè)網(wǎng)絡(luò)營銷推廣處點(diǎn)
前言:
相信很多小伙伴對緩存鎖都不陌生,但是簡單的緩存鎖想要用好還是需要一些功力。本文總結(jié)了筆者多年使用緩存所的一些心得,歡迎交流探討~
冪等模型:
冪等場景一般由查重+寫入
兩步操作組成,兩步操作組成一個(gè)最小完整邏輯,再通過緩存鎖保證原子性
實(shí)現(xiàn):
實(shí)現(xiàn)redis分布式鎖需要注意兩個(gè)關(guān)鍵點(diǎn):保證原子性、設(shè)置過期時(shí)間
保證原子性的目的:是為了保證同一時(shí)間只有一個(gè)請求能獲取到鎖
設(shè)置過期時(shí)間的目的:是為了防止解鎖失敗導(dǎo)致死鎖
public class MethodLock {private static final Logger logger = LoggerFactory.getLogger(MethodLock.class);public static final String KEY_SEPARATOR = ":";public static final String PARAM_SEPARATOR = "_";public static final String KEY_PARAM_SEPARATOR = "#";public static final String SET_SUCCESS_RESULT = "OK";public static final String METHOD_LOCK = "doraemon:method:lock";private static Jedis jedis;private static Jedis getJedis() {if (jedis == null) {jedis = 自行實(shí)現(xiàn);}return jedis;}/*** 獲取鎖** @param methodName 方法名,類名加方法名(例:MethodLock.getLock)* @param timeout 鎖過期時(shí)間(單位秒)* @param params 參數(shù)(通過參數(shù)控制鎖的粒度)* @return*/public static boolean getLock(String methodName, long timeout, String... params) {String method = "getLock|獲取鎖|";try {String key = getKey(methodName, params);String result = getJedis().set(key, methodName, "nx", "ex", timeout <= 0 ? 5 : timeout);if (Objects.equals(SET_SUCCESS_RESULT, result)) {return true;}} catch (Exception e) {logger.error(method + "執(zhí)行失敗,methodName={},timeout={},params={}", methodName, timeout, JSONObject.toJSONString(params), e);}return false;}/*** 釋放鎖** @param methodName 方法名,類名加方法名(例:MethodLock.getLock)* @param params 參數(shù)(通過參數(shù)控制鎖的粒度)* @return*/public static boolean delLock(String methodName, String... params) {String method = "delLock|釋放鎖|";try {String key = getKey(methodName, params);long result = getJedis().del(key);if (result > 0) {return true;}} catch (Exception e) {logger.error(method + "執(zhí)行失敗,methodName={},params={}", methodName, JSONObject.toJSONString(params), e);}return false;}/*** 【私有方法】獲取redis key** @param methodName* @param params* @return*/protected static String getKey(String methodName, String... params) {if (StringUtils.isBlank(methodName)) {return null;}String key = METHOD_LOCK + KEY_SEPARATOR + methodName;if (params != null && params.length > 0) {key += KEY_PARAM_SEPARATOR + Joiner.on(PARAM_SEPARATOR).useForNull("null").join(params);}return key;}
注意事項(xiàng):
1、鎖范圍內(nèi)的邏輯需要 完整+精簡
鎖范圍內(nèi)指的是:獲取鎖和釋放鎖中間的邏輯,只有必須保證原子性的關(guān)鍵的邏輯才能放入到鎖范圍內(nèi),其他的一些不相關(guān)邏輯完全可以放在鎖范圍外處理
完整:加鎖的目的是為了解決并發(fā)問題,所以鎖里面的邏輯要求完整,不完整的邏輯=沒加鎖
比如冪等場景下:一般是 查重+寫入
兩個(gè)操作需要保證原子性,這兩個(gè)操作加起來就是一個(gè)完整邏輯
精簡:精簡的意思是在保證完整的前提下,不要有多余的邏輯,以免鎖占用時(shí)間過長,進(jìn)而影響性能
2、鎖粒度選擇要恰當(dāng)
鎖粒度指的是:鎖對請求條件篩選的粗細(xì)程度,例如用戶購買商品下單時(shí)可以 1、根據(jù)用戶id
加鎖,2、也可以根據(jù)用戶id + 商品id
加鎖,1和2就是兩種粒度
鎖的粒度太大會導(dǎo)致鎖的范圍過大,可能會影響當(dāng)前邏輯之外的業(yè)務(wù)
鎖的粒度太小會導(dǎo)致鎖的范圍過小,可能會導(dǎo)致鎖失效
3、鎖過期時(shí)間設(shè)置要合理
一般建議過期時(shí)間 = 邏輯執(zhí)行時(shí)間 * 150%
過期時(shí)間太小可能導(dǎo)致在邏輯執(zhí)行完成前,鎖過期失效
過期時(shí)間太大+解鎖失敗,可能導(dǎo)致在鎖過期失效之前重試的請求被拒之門外
4、加鎖在try之前,鎖中的邏輯使用try catch包圍,解鎖在finally里處理
若加鎖操作在try catch中,并發(fā)場景下未獲取鎖的操作會執(zhí)行到finally里將鎖解除,影響正常邏輯
防止邏輯中出現(xiàn)異常,阻斷流暢,導(dǎo)致解鎖操作執(zhí)行不到
5、基于redis的分布式鎖并不100%可靠
在一些特殊情況下,比如redis宕機(jī),數(shù)據(jù)丟失,可能會導(dǎo)致鎖失效