更高效地提高redis client多線程操作的并發吞吐設計
Redis是一個非常高效的基于內存的NOSQL數據庫,它提供非常高效的數據讀寫效能.在實際應用中往往是帶寬和CLIENT庫讀寫損耗過高導致無法更好地發揮出Redis更出色的能力.下面結合一些redis本身的特性和一些client操作上的改變來提高整個redis操作的效能.
上圖是反映平常操作redis的情況,每個線程都獨立的發起相應連接對redis的網絡讀寫.雖然我們可以通過批操作的方式來把當前多個操作合并成一個,但這種方式只能針對當單線程,而多線程相互合并則設計上很少關注.從redis的協議來說其實并沒有限制,只是在client庫的設計一般沒有考慮進去.
如果在多線程操作REDIS的同時如果能夠合并網絡操作,那意味著可以減少操作網絡讀寫的情況把處理能力提升到最大化.這樣Client總體的性能都會有所提升,而REDIS也因表層的網絡讀取減少而達到更好的利用率.
以上是設計圖,原理并不復雜,其實就是把每個請求的操作放到一個隊列中,后面開啟一個線程來把前面的指令進行一個合并操操作.一個線程在高并發下可以無法更快速地合并起來,可以根據需要進行合理的操作線程應用.
這種設計的效果是否真的比較理想呢,以下是一個簡單的測試
public void AnycSet() { CodeTimer.Time("beetle.redis asynset", () => { Parallel.For(0, Count, x => { ProtobufKey key = x.ToString(); key.AsynSet(new User() { UserId = x, NickName = "sdffffffffffffffffffffffffffffffffffffffffsdffffffffffffffffffffffffffffffffffffffffbeetlesdffffffffffffffffffffffffffffffffffffffffsdffffffffffffffffffffffffffffffffffffffff" + x }); }); }); } public void Set() { CodeTimer.Time("beetle.redis set", () => { Parallel.For(0, Count, x => { ProtobufKey key = x.ToString(); key.Set(new User() { UserId = x, NickName = "sdffffffffffffffffffffffffffffffffffffffffsdffffffffffffffffffffffffffffffffffffffffbeetlesdffffffffffffffffffffffffffffffffffffffffsdffffffffffffffffffffffffffffffffffffffff" + x }); }); }); }
測試結果如下
以上是10W次的操作測試結果,由于redis在本機所以交互非常可觀.
雖然在多線程高并發下這樣的設計可以把吞吐能力和效能有一個非常不錯的效果,但其也存在缺陷因為每次操作都經過不同線程的調處理,如果并發線程不多操作密集度不高.那效果并不理想;因為網絡操作密集度不高,可得到并合的數量不多,這方面的損耗有可能低于操作跨線程調度所帶來的損耗.
來自:http://my.oschina.net/ikende/blog/411744