利用DB實現一個業務鎖例子
阿新 • • 發佈:2020-08-28
由於網際網路的發展,現在大部分專案都是分散式架構多機部署的,為了防止某些需要單一執行的操作同時被多臺伺服器執行了,就需要在這些業務點上加上分散式鎖,來防止多伺服器的併發和重複job。常見的場景比方說給使用者傳送一些訊息,如果同一時刻N臺伺服器獲取到了任務,那麼結果將不堪設想,很容易對使用者形成了資訊轟炸的效果,如果再觸發一些限流機制,那直接影響後續的業務進行了。
下面這個例子呢,只是為了更好的理解鎖的概念,生產中常用的鎖可以使用redis的分散式鎖機制。
鎖模型SQl
CREATE TABLE `lock` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `key` varchar(128) NOT NULL COMMENT '鎖唯一性Key', `create_time` datetime NOT NULL COMMENT '建立時間', `expire_time` bigint(20) unsigned NOT NULL COMMENT '鎖過期時間戳', PRIMARY KEY (`id`), UNIQUE KEY `uk_lock` (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
簡單的鎖模型
@TableName("lock") @Data public class LockEntity { /** * 自增的id,當解鎖的時候做刪除操作時用作樂觀鎖條件 */ @Version private Long id; /** * 鎖的key,唯一 */ private String key; /** * 過期時間 */ private Long expireTime; /** * 建立時間 */ private Date createTime; /** * db的當前時間,用於判斷是否過期 */ private Long dbTime; }
提供鎖能力的Service
public interface LockService {
/**
* 嘗試獲得鎖
*/
LockEntity tryLock(String key, Long seconds);
/**
* 解鎖
*/
Boolean unLock(String key, Long lockId);
}
具體實現ServiceImpl
@Service public class LockServiceImpl implements LockService { @Autowired private LockMapper lockMapper; @Override public LockEntity tryLock(String key, Long seconds) { try { return lock(key, seconds); } catch (Exception e) { //獲得鎖過程中出錯了 return null; } } private LockEntity lock(String key, Long seconds) { //每次上鎖之前先校驗 LockEntity lockEntity = lockMapper.selectByKey(key); /** * 如果鎖存在了 * 判斷過期時間,如果沒有過期,說明正在有執行緒持有鎖 * 如果已經過期了,說明由於某種原因沒有釋放鎖 */ if (lockEntity != null) { if (lockEntity.getDbTime() < lockEntity.getExpireTime()) { return null; } else { /** * 如果多個執行緒刪除同一個鎖,必須用id作為樂觀鎖進行操作 * 哪個執行緒刪除成功了,就有獲得鎖的權力 */ if (!removeLock(key, lockEntity.getId())) { return null; } } } lockMapper.insert(key, new Date(), seconds); return lockMapper.selectByKey(key); } @Override public Boolean unLock(String key, Long lockId) { return removeLock(key, lockId); } private boolean removeLock(String key, Long id) { return lockMapper.delete(Wrappers.<LockEntity>lambdaQuery() .eq(LockEntity::getKey, key) .eq(LockEntity::getId, id)) > 0; } }
其他的一些sql(由於僅僅作為筆記,所以沒有按照mybatis規範去實現持久層程式碼)
@Mapper
public interface LockMapper extends BaseMapper<LockEntity> {
String columns = "id,key,create_time as createTime,expire_time as expireTime,unix_timestamp(now()) as dbTime ";
@Select("select " + columns + "form lock where #{key}")
LockEntity selectByKey(String key);
@Insert("insert into lock (key,create_time,expire_time) values (#{key,#{createTime},unix_timestamp(now())+#{expireTime})")
void insert(String key, Date createDate,Long expireTime);
}
這個小例子呢,其實是利用了mysql的唯一主鍵的排他性,將DB的排他能力運用到了我們的業務服務中。
此外,瞭解了這樣的思路之後,我們是不是也可以用一些其他第三方的服務特性來實現業務鎖呢,比如服務註冊中心,服務的註冊id也是唯一的,所以上鎖的過程其實就是向服務註冊中心註冊服務,誰成功了,誰就相當於獲得鎖了。