這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
最近在寫一個用於儲存protobuf配置的組態管理服務,商務邏輯不難,2天就搞定,但是後續bug不少,也踩了很多坑,在這裡記錄下。
首先,一直以為golang內的sql模組是單連結的,所以一開始在每個goroutine內都open了一個DB,並寫了一個串連池進行管理。後續發現是多此一舉,白白寫了好多代碼。golang的sql模組內建串連池功能,在執行sql語句的時候才會分配串連,執行完畢後歸還給串連池,所以假設用golang的sql模組,一個程式一個DB就行了。
既然有串連池的支援,那麼也要注意千萬不要泄露串連池的串連。假設你採用Query來執行查詢語句,那麼會返回一個sql.Rows結構,這個結構會佔用一個串連,只有在遍曆完才會自動關閉,所以最好是獲得了Rows後執行一次Rows的Close方法,多次Close是沒事的。
然後,因為被上級否定了使用transaction的想法,只能在程式內進行事務控制。一開始整個sql執行model共用一個讀寫鎖,在執行效能測試的情況下,讀的tps在3k左右,效能還行,可是寫卻只是200。這是無法接受的,後來仔細分析了下代碼,在寫前面鎖了寫,那麼並行的幾個routine會等待佔有鎖的那個routine寫入完畢才會有第二個routine進行寫操作,就等於白白的排隊了,而測試案例是insert新的記錄而已,不會有衝突的問題。現在想想一個鎖雖然寫起來方便,但是效能影響很大,於是今天寫了一個新的讀寫鎖管理器,綁定特定的key,每一個key在當前key的鎖被佔用的情況下,會返回被佔用的鎖,並且將引用計數值+1,假設沒有對應的鎖,則返回新的鎖。釋放鎖的時候,判定當前key的鎖的引用值,假設已經為0了,說明沒有被其它routine進行鎖wait,則銷毀這個鎖,否則引用計數值-1。
這樣的話,將不同的sql產生一個key,採用這個key來進行寫衝突管理,當兩個sql有相同的key的時候,則會進行鎖競爭,假設key不相同,則不會有競爭。同時採用引用計數來避免讀寫鎖的泄露,對長期穩定運行伺服器有好處。
讀操作的話,則當前沒有寫鎖的情況下,則直接進行讀取,所以讀的效能不會有很大的影響。
在這個新的鎖的設計下,tps從200提升到了1200,算是可接受的範圍了。這個方案的關鍵點在於key的產生,在於提取每個sql操作影響的行,只要能得到這個key,則產生讀寫鎖將十分方便。