引言
鎖是開發過程中十分常見的工具,你一定不陌生,悲觀鎖,樂觀鎖,排它鎖,公平鎖,非公平鎖等等,很多概念,如果你對java裡的鎖還不了解,可以參考這篇:不可不說的Java「鎖」事,這篇寫的很全面了,但是對於初學者,知道這些鎖的概念,由於缺乏實際工作經驗,可能並不了解鎖的實際使用場景,Java中可以透過Volatile、Synchronized、ReentrantLock 三個關鍵字來實現線程的安全,這部分知識在第一輪基礎面試裡一定會問(要熟練哦)。
在分散式系統中Java這些鎖技術是無法同時鎖住兩台機器上的程式碼,所以要透過分散式鎖來實現,熟練使用分散式鎖也是大廠開發必會的技能。
1、面試官:
你有遇到需要使用分散式鎖定的場景嗎?
問題分析:這個問題主要作為引子,先要了解什麼場景下需要使用分散式鎖,分散式鎖要解決什麼問題,在此前提下有助於你更好的理解分散式鎖的實現原理。
使用分散式鎖定的場景一般需要滿足以下場景:
系統是一個分散式系統,java的鎖已經鎖不住了。
操作共享資源,例如庫裡唯一的使用者資料。
同步訪問,即多個行程同時操作共享資源。
答案:說一個我在專案中使用分散式鎖定場景的例子:
消費積分在很多系統裡都有,信用卡,電商網站,透過積分換禮品等,這裡「消費積分」這個操作是需要使用鎖的典型場景。
事件A:
以積分兌換禮品為例來講,完整的積分消費過程簡單分成3步驟:
A1:使用者選取商品,發起兌換提交訂單。
A2:系統讀取使用者剩餘積分:判斷使用者目前積分是否足夠。
A3:扣掉用戶積分。
事件B:
系統發給使用者積分也簡單分成3步驟:
B1:計算使用者當天應得積分
B2:讀取用戶原有積分
B3:在原有積分上增加本次應得積分
那麼問題來了,如果用戶消費積分和用戶累加積分同時發生(同時用戶積分進行操作)會怎樣?
假設:用戶在消費積分的同時恰好離線任務在計算積分給用戶發放積分(如根據用戶當天的消費額),這兩件事同時進行,下面的邏輯有點繞,耐心理解。
用戶U有1000點(記錄用戶積分的資料可以理解為共享資源),本次兌換要消耗掉999點。
不加鎖的情況:事件A程式在執行到第2步讀取積分時,A:2操作讀到的結果是1000分,判斷剩餘積分夠本次兌換,緊接著要執行第3步驟A:3操作扣積分(1000 - 999 = 1),正常結果應該是使用者還是1分。但是這個時候事件B也在執行,這次給用戶U發放100積分,兩個線程同時進行(同步訪問),不加鎖的情況,就會有下面這種可能,A:2 -> B :2 -> A:3 -> B:3 ,在A:3尚未完成前(扣積分,1000 - 999),用戶U總積分被事件B的線程讀取了,最後用戶U的總積分變成了1100分,還白白兌換了一個999積分的禮物,這顯然不符合預期結果。
有人說怎麼可能這麼巧同時操作用戶積分,cpu那麼快,只要用戶夠多,並發量夠大,墨菲定律遲早生效,出現上述bug只是時間問題,還有可能被黑產業界卡住這個bug瘋狂薅羊毛,這個時候作為開發人員要解決這個隱患就必須了解鎖的使用。
(寫程式碼是一項嚴謹的事兒!)
Java本身提供了兩種內建的鎖的實現,一種是由JVM實現的synchronized 和JDK 提供的Lock,而很多原子操作類別都是線程安全的,當你的應用是單機或說單進程應用時,可以使用這兩種鎖來實現鎖。
但是當下網路公司的系統幾乎都是分散式的,這時候Java自帶的synchronized 或Lock 已經無法滿足分散式環境下鎖的要求了,因為程式碼會部署在多台機器上,為了解決這個問題,分散式鎖應運而生,分散式鎖的特點是多進程,多個實體機器上無法共享內存,常見的解決方法是基於內存層的干涉,落地方案就是基於Redis的分散式鎖or ZooKeeper分散式鎖定。
(我分析的不能更詳細了,面試官再不滿意?)
2、面試官:
Redis分散式鎖定實作方法
目前有兩種主要的實作方式來解決分散式鎖定問題:一種是基於Redis Cluster模式,另一種則是…。 2.基於Zookeeper 集群模式。
優先掌握這兩種,應付面試基本上沒問題了。
答:
1. Distributed lock based on Redis
Method 1: Use the setnx command to lock
public static void wrongGetLock1(Jedis jedis, String lockKey, String requestId, int expireTime) { // 第一步:加锁 Long result = jedis.setnx(lockKey, requestId); if (result == 1) { // 第二步:设置过期时间 jedis.expire(lockKey, expireTime); } }
Code explanation:
setnx
command, It means set if not exist. If the lockKey does not exist, store the key in Redis. If the result returns 1 after successful saving, it means the setting is successful. If it is not 1, it means failure. Other threads have already set it.
expire()
, set the expiration time to prevent deadlock. Assume that if a lock is not deleted after being set, then the lock is equivalent to always existing, resulting in a deadlock.
(At this point, I would like to emphasize a "but" to the interviewer)
Think about it, what are the flaws in my method above? Continue to explain to the interviewer...
There are two steps to locking. The first step is jedis.setnx, and the second step is jedis.expire to set the expiration time. setnx and expire are not an atomic operation. If the program is executed after the An exception occurred after the first step. In the second step, jedis.expire(lockKey, expireTime) was not executed, which means that the lock has no expiration time, and a deadlock may occur. How to improve this problem?
Improvement:
public class RedisLockDemo { private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * 获取分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public static boolean getLock(Jedis jedis, String lockKey, String requestId, int expireTime) { // 两步合二为一,一行代码加锁并设置 + 过期时间。 if (1 == jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime)) { return true;//加锁成功 } return false;//加锁失败 } }
Code explanation:
Combine locking and setting expiration time into one, one line of code, atomic operation.
(The interviewer was very satisfied before asking further questions)
3. Interviewer: What about the unlocking operation?
Answer:
Releasing the lock means deleting the key
Use the del command to unlock
public static void unLock(Jedis jedis, String lockKey, String requestId) { // 第一步: 使用 requestId 判断加锁与解锁是不是同一个客户端 if (requestId.equals(jedis.get(lockKey))) { // 第二步: 若在此时,这把锁突然不是这个客户端的,则会误解锁 jedis.del(lockKey); } }
Code explanation: Use the requestId to determine whether the lock and unlock are the same The two steps of client and jedis.del(lockKey) are not atomic operations. In theory, it will appear after executing the first if judgment operation that the lock has actually expired and been acquired by other threads. This is the time to execute jedis.del(lockKey ) operation is equivalent to releasing someone else's lock, which is unreasonable. Of course, this is a very extreme situation. If there are no other business operations in the first and second steps of the unLock method, throwing the above code online may not really cause problems. The first reason is the business concurrency. If it is not high, this flaw will not be exposed at all, so the problem is not big.
But writing code is rigorous work, and to be perfect, you must be perfect. Improvements are proposed to address the problems in the above code.
Code improvement:
public class RedisTool { private static final Long RELEASE_SUCCESS = 1L; /** * 释放分布式锁 * @param jedis Redis客户端 * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
Code explanation:
Use the eval method of the jedis client and just one line of script to solve the atomicity problem involved in method one.
3. Interviewer:
Distributed lock implementation principle based on ZooKeeper
Answer: This is still an example of point consumption and point accumulation: event A and event B need to be carried out at the same time The points modification operation is performed on two machines at the same time. The correct business logic is to let one machine execute it first and then the other machine. Either event A is executed first, or event B is executed first, so as to ensure that A will not occur. :2 -> B:2 -> A:3 -> B:3 The more points you spend, the more points you spend (thinking that once this kind of bug goes online, the boss will be angry, I may cry).
what to do? Use zookeeper distributed locks.
After a machine receives the request, it first obtains a distributed lock on zookeeper (zk will create a znode) and performs the operation; then another machine also tries to create the znode, but finds that it cannot create it. , because it was created by someone else, you can only wait until the first machine finishes executing before you can get the lock.
Using the sequential node feature of ZooKeeper, if we create 3 nodes in the /lock/ directory, the ZK cluster will create the nodes in the order in which they are created. The nodes are divided into /lock/0000000001, /lock/0000000002 , /lock/0000000003, the last digit is incremented in sequence, and the node name is completed by zk.
ZK also has a node called a temporary node. The temporary node is created by a client. When the client disconnects from the ZK cluster, the node is automatically deleted. EPHEMERAL_SEQUENTIAL is a temporary sequence node.
The basic logic of distributed locks is to use the presence or absence of nodes in ZK as the lock status to implement distributed locks
The client calls the create() method Create a temporary sequence node named "/dlm-locks/lockname/lock-".
The client calls the getChildren("lockname") method to obtain all created child nodes.
After the client obtains the paths of all child nodes, if it finds that the node it created in step 1 has the smallest sequence number among all nodes, it will check whether the sequence number it created ranks first. First, if it is first, then it is considered that this client has obtained the lock, and no other client has obtained the lock before it.
If the created node is not the smallest of all nodes, then it is necessary to monitor the largest node with a smaller sequence number than the node it created, and then enter the waiting state. After the monitored child node changes, obtain the child node and determine whether to obtain the lock.
Although the process of releasing the lock is relatively simple, which is actually deleting the created child node, you still need to consider abnormal situations such as failure to delete the node.
Additional supplement
Distributed locks can also solve the problem from the database
方法一:
利用 Mysql 的锁表,创建一张表,设置一个 UNIQUE KEY 这个 KEY 就是要锁的 KEY,所以同一个 KEY 在mysql表里只能插入一次了,这样对锁的竞争就交给了数据库,处理同一个 KEY 数据库保证了只有一个节点能插入成功,其他节点都会插入失败。
这样 lock 和 unlock 的思路就很简单了,伪代码:
def lock : exec sql: insert into locked—table (xxx) values (xxx) if result == true : return true else : return false def unlock : exec sql: delete from lockedOrder where order_id='order_id'
方法二:
使用流水号+时间戳做幂等操作,可以看作是一个不会释放的锁。
以上是Redis分散式鎖怎麼實現及應用場景是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Redis的數據模型和結構包括五種主要類型:1.字符串(String):用於存儲文本或二進制數據,支持原子操作。 2.列表(List):有序元素集合,適合隊列和堆棧。 3.集合(Set):無序唯一元素集合,支持集合運算。 4.有序集合(SortedSet):帶分數的唯一元素集合,適用於排行榜。 5.哈希表(Hash):鍵值對集合,適合存儲對象。

Redis的數據庫方法包括內存數據庫和鍵值存儲。 1)Redis將數據存儲在內存中,讀寫速度快。 2)它使用鍵值對存儲數據,支持複雜數據結構,如列表、集合、哈希表和有序集合,適用於緩存和NoSQL數據庫。

Redis是一個強大的數據庫解決方案,因為它提供了極速性能、豐富的數據結構、高可用性和擴展性、持久化能力以及廣泛的生態系統支持。 1)極速性能:Redis的數據存儲在內存中,讀寫速度極快,適合高並發和低延遲應用。 2)豐富的數據結構:支持多種數據類型,如列表、集合等,適用於多種場景。 3)高可用性和擴展性:支持主從復制和集群模式,實現高可用性和水平擴展。 4)持久化和數據安全:通過RDB和AOF兩種方式實現數據持久化,確保數據的完整性和可靠性。 5)廣泛的生態系統和社區支持:擁有龐大的生態系統和活躍社區,

Redis的關鍵特性包括速度、靈活性和豐富的數據結構支持。 1)速度:Redis作為內存數據庫,讀寫操作幾乎瞬時,適用於緩存和會話管理。 2)靈活性:支持多種數據結構,如字符串、列表、集合等,適用於復雜數據處理。 3)數據結構支持:提供字符串、列表、集合、哈希表等,適合不同業務需求。

Redis的核心功能是高性能的內存數據存儲和處理系統。 1)高速數據訪問:Redis將數據存儲在內存中,提供微秒級別的讀寫速度。 2)豐富的數據結構:支持字符串、列表、集合等,適應多種應用場景。 3)持久化:通過RDB和AOF方式將數據持久化到磁盤。 4)發布訂閱:可用於消息隊列或實時通信系統。

Redis支持多種數據結構,具體包括:1.字符串(String),適合存儲單一值數據;2.列表(List),適用於隊列和棧;3.集合(Set),用於存儲不重複數據;4.有序集合(SortedSet),適用於排行榜和優先級隊列;5.哈希表(Hash),適合存儲對像或結構化數據。

Redis計數器是一種使用Redis鍵值對存儲來實現計數操作的機制,包含以下步驟:創建計數器鍵、增加計數、減少計數、重置計數和獲取計數。 Redis計數器的優勢包括速度快、高並發、持久性和簡單易用。它可用於用戶訪問計數、實時指標跟踪、遊戲分數和排名以及訂單處理計數等場景。

使用 Redis 命令行工具 (redis-cli) 可通過以下步驟管理和操作 Redis:連接到服務器,指定地址和端口。使用命令名稱和參數向服務器發送命令。使用 HELP 命令查看特定命令的幫助信息。使用 QUIT 命令退出命令行工具。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境