最近寫了一個服務器,業務邏輯很簡單,每個協議包往服務器上報數據, 每個數據包中可能有N塊數據需要保存在數據庫中的.顯然, 這個業務邏輯是不能使用類似memcached這樣的緩存的, 因為每條數據都是相對獨立的, 而且必須保證每個數據都保存到數據庫中.這里拋開服務器最基本的那些IO模型之類的不說,談談對這個服務器的幾個優化步驟.
1) 最簡單的處理
最簡單的處理就是按部就班的,每條數據老老實實的插入到數據庫中.顯然, 這樣做的效率是低的, 如果并發量大的時候,mysql負載變大,而服務器阻塞在數據庫操作上, 導致處理連接比較慢.
2) 第一步優化:定量插入數據
這里基本的模型不變, 只是不是每個數據一來就插入, 而是緩存起來, 等到積累到了一定量才開始一起插入到數據庫中.這種方式比第一種方式并不能獲得太大的提高.
3) 第二步優化:開線程處理往數據庫中插入數據
在這里, 多開一個線程用于往數據庫中插入數據, 同時開辟了一個緩沖區, 主線程不停的往這個緩沖區中倒數據, 副線程負責將緩沖區中的數據導入數據庫.用偽代碼來表示, 這個優化就是這樣的:
主線程:
往緩沖區A中添加數據(注意這里不需要加鎖)
副線程
加鎖
將緩沖區A,B的指針互換, 這樣緩沖區B就指向了原來緩沖區A,A指向B緩沖區
解鎖
將緩沖區B的數據導入數據庫
清空B緩沖區
這樣的優化效率獲得了極大的提高, 主線程只需要負責與客戶端之間的通信, 接收客戶端的數據, 而副線程只需要將數據導入緩沖區, 不需要關心通信問題.這樣,主線程就不會由于插入數據緩慢而導致接收數據和新的連接緩慢了.而由于是多線程, 主副線程之間的數據共享很容易做到, 主線程往緩沖區插入數據的時候甚至不需要加鎖, 而副線程倒數據的時候只需要加鎖然后把兩個緩沖區的指針互調就行了(我懷疑這個加鎖也是可以免去的).各司其職,又各不騷擾,簡單而高效.
4)第三步優化:將向數據庫導入數據的時間盡可能的平均分布
這一步與上一步大體相同, 只是在副線程倒數據的時候定一個量, 當計數器達到這個量時, 副線程休眠一陣(比如sleep一秒).這樣的好處是可以將向數據庫導入數據的時間盡可能的平均分布, 減小峰值時間點數據庫的壓力.
5)第四步優化:從數據庫角度進行優化
因為服務器只需要往數據庫中插入數據, 可以考慮在插入的時候將同一個表的數據緩存在一起, 然后寫在一條sql語句中一起插入, 這樣減少了sql語句的調用, IO減少了, 效率也提高了.我使用的是mysql, 關于mysql的優化插入數據, 可以參考
這里.
經過這幾個優化步驟之后, 服務器的效率比之最開始有了極大的提高, 不僅是服務器的效率提高了, 整個系統的IO,數據庫壓力也減少了.
以下是分割線:
-------------------------------------------------------------------
我仔細想了一下,覺得似乎真的可以去掉加鎖的步驟...
我的緩沖區是一個STL的list鏈表,主線程不停的往list中push_back.
而交換緩沖區的時候調用的是STL中的swap操作, 也就是交換兩個鏈表的頭結點而已.
從這里來看,確實可以省去加鎖的步驟的.
--------------------------------------------------------------------
第二天,我決定還是加鎖了.由于臨界區內的操作并不多, 我想效率沒有太大的影響.