問題描述:
SQLite數據庫同一時刻只允許單個線程寫入,很多服務端程序會開很多線程,每個線程為一個客戶端服務,如果有多個客戶端同時發起寫入請求,在服務端會因為某個線程尚未寫入完成尚未解除對數據庫的鎖定而導致其他線程無法在限定的時間內完成寫入操作而拋出異常,提示“database is locked”。
如果編寫高並發的服務端程序,一定要對數據庫的寫入操作進行有效管理,常用的方案有兩個:1)使用鎖機制使得多個線程競爭進入臨界區,確保同一時刻只有一個線程執行寫入數據庫的代碼;2)連接數據庫時設置參數timeout,設置當數據庫處於鎖定狀態時最長等待時間,sqlite3.connect()函數的參數timeout默認值為5秒,不適合服務端程序。但是參數timeout設置為多少更合適取決於具體的應用場景,雖然形式簡潔,但是不如第一種方法通用。
演示代碼如下,可以注釋掉兩種方案的代碼,多次運行程序觀察運行狀態。
或者使用線程鎖LOCK
使用redis-lock鎖機制,解決並發帶來的問題
python3 使用 python-redis-lock 編寫鎖,解決並發計算問題 - 雲 + 社區 - 騰訊雲我在最近的一個任務中,存在一個 redis 高並發計算多個客戶端接收預警信息的時長問題。
需求
我在最近的一個任務中,存在一個 redis 高並發計算多個客戶端接收預警信息的時長問題。
模型是首先模擬多個客戶端連接預警服務器集群,然后向預警服務集群發送告警信息。隨后預警服務集群將會向客戶端推送告警信息。
此時,我記錄了發送告警至預警集群的時間,並且在客戶端還會記錄接收到告警的時間。
我將這個時間都會記錄到 redis 中,那么此時就會有一個問題,當多個客戶端搶占式往 redis 讀取數據,計算,設置數據,這個過程是會被相互覆蓋的。

可以從上面的截圖來看,多個不同的客戶端讀取 redis 的數據,大部分讀取到了同一個數據,導致計算錯誤。
導致問題的示意圖如下:

為了解決這個問題,則可以編寫一個 redis 的鎖,用來控制數據的並發讀取以及寫入。 在 python redis 庫默認只有樂觀鎖的一種寫法,在這里我再推薦使用一個庫 python-redis-lock,使用這個庫對 redis 多個客戶端並發的情況加鎖,真的很方便。 下面來看看怎么使用。
python-redis-lock
https://pypi.org/project/python-redis-lock/
在使用這個庫之前,需要安裝如下:
pip install python-redis-lock
使用鎖的示例:
lock = redis_lock.Lock(conn, "name-of-the-lock") if lock.acquire(blocking=False): print("Got the lock.") lock.release() else: print("Someone else has the lock.")
上面是單獨設置鎖的方式,還可以單獨設置所有 redis 的操作加入鎖。
# On application start/restart import redis_lock redis_lock.reset_all(redis_client)
修改業務代碼,增加 lock 操作
1. 首先導入 redis_lock
import redis_lock
2. 將 redis 連接的客戶端傳入 lock 中,並設置 lock 的名稱
# 設置redis連接 self.conn = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True, db=3) # 設置redis鎖 self.lock = redis_lock.Lock(self.conn, "redis-lock")
3. 將業務讀取、設置 redis 的部分加入鎖
while True: # 設置redis鎖,操作redis if self.lock.acquire(blocking=False): print("Got the lock.") # 獲取lock,執行業務處理 # 獲取當前redis鍾記錄的客戶端接收到告警的總時長 recv_time_sum_count_clients = self.conn.get(recv_time_sum_count_clients_key) if recv_time_sum_count_clients is None: recv_time_sum_count_clients = "0:0" # 獲取當前的統計數據 recv_time_sum, count_clients = recv_time_sum_count_clients.split(":") # 計算告警接收總時長 recv_time_sum = float(recv_time_sum) + recv_time # 計算收到預警的客戶端數量 count_clients = int(count_clients) + 1 # 寫入redis中 recv_time_sum_count_clients = "%s:%s" % (str(recv_time_sum), str(count_clients)) self.conn.set(recv_time_sum_count_clients_key, recv_time_sum_count_clients) print("user_id = %s, 計算平均時間成功, " "recv_time_sum = %s, count_clients = %s \n" % (self.user_id, recv_time_sum, count_clients)) # 釋放lock self.lock.release() # 退出循環 break else: print("Someone else has the lock.")
在客戶端的代碼中設置了鎖之后,再來執行一下,看看有無搶占讀取 redis 數據的情況,如下:

設置了鎖之后,客戶端由於並發導致 redis 數據讀取、設置錯誤的情況就可以避免了。