JUC併發容器——跳錶

跳錶 SkipList

  • 跳錶是隨機化的一個數據結構,以O(logn)的期望時間支持查找和插入。
  • 跳錶是鏈表的優化,在有序鏈表的基礎上,它把一維的線性鏈表做了一些提取,相當於新建了若干層索引,借索引減少比較次數。
  • JDK中沒有SkipList的代碼實現(和其他數據結構相比,其實並不複雜),但卻有Doug Lea書寫的ConcurrentSkipList

https://www.jianshu.com/p/d12389b80a19

在網上學習了一些材料。
https://www.baidu.com/link?url=bQLDt5r-VYz3-_s57DWkzcWtM8o0He-asgeHBPRc9mTKoPnPoUQ3eaPEwESkJZ_19HDfaXVUFIRTWggzzYDQpq&wd=&eqid=a940f0ba001a8a4c000000065daad90a
這一篇:https://www.zhihu.com/question/30527705

複製代碼
AVL樹:最早的平衡二叉樹之一。應用相對其他數據結構比較少。windows對進程地址空間的管理用到了AVL樹

紅黑樹:平衡二叉樹,廣泛用在C++的STL中。map和set都是用紅黑樹實現的。我們熟悉的STL的map容器底層是RBtree,當然指的不是unordered_map,後者是hash。

B/B+樹用在磁盤文件組織 數據索引和數據庫索引

Trie樹 字典樹,用在統計和排序大量字符串


AVL是一種高度平衡的二叉樹,所以通常的結果是,維護這種高度平衡所付出的代價比從中獲得的效率收益還大,故而實際的應用不多,
更多的地方是用追求局部而不是非常嚴格整體平衡的紅黑樹。當然,如果場景中對插入刪除不頻繁,只是對查找特別有要求,AVL還是優於紅黑的。

紅黑樹的應用就很多了,除了上面同學提到的STL,還有
epoll在內核中的實現,用紅黑樹管理事件塊
nginx中,用紅黑樹管理timer等
Java的TreeMap實現
著名的linux進程調度Completely Fair Scheduler,用紅黑樹管理進程控制塊

B和B+主要用在文件系統以及數據庫中做索引等,比如Mysql:B-Tree Index in MySql

trie 樹的一個典型應用是前綴匹配,比如下面這個很常見的場景,在我們輸入時,搜索引擎會給予提示
還有比如IP選路,也是前綴匹配,一定程度會用到trie


跳錶:Redis中就使用跳錶,而不是紅黑樹來存儲管理其中的元素(應該說的是一級元素-直接的Key,裏面的value應該是有不同的數據結構)。

首先,跳錶是skiplist?不是ziplist。ziplist在redis中是一個非常省內存的鏈表(代價是性能略低),所以在hash元素的個數很少(比如只有幾十個),
那麼用這個結構來存儲則可以在性能損失很小的情況下節約很多內存(redis是內存數據庫啊,能省還是要省的)。好這個問題清楚了。

在server端,對併發和性能有要求的情況下,如何選擇合適的數據結構(這裏是跳躍表和紅黑樹)。
如果單純比較性能,跳躍表和紅黑樹可以說相差不大,但是加上併發的環境就不一樣了,
如果要更新數據,跳躍表需要更新的部分就比較少,鎖的東西也就比較少,所以不同線程爭鎖的代價就相對少了,
而紅黑樹有個平衡的過程,牽涉到大量的節點,爭鎖的代價也就相對較高了。性能也就不如前者了。
在併發環境下skiplist有另外一個優勢,紅黑樹在插入和刪除的時候可能需要做一些rebalance的操作,這樣的操作可能會涉及到整個樹的其他部分,
而skiplist的操作顯然更加局部性一些,鎖需要盯住的節點更少,因此在這樣的情況下性能好一些。
複製代碼
另外Redis作者描述的使用跳錶的原因:

複製代碼
請看開發者說的,他爲什麼選用skiplist The Skip list

There are a few reasons:

  1. They are not very memory intensive. It’s up to you basically.
    Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive
    than btrees.
    注:跳錶的一個缺點是耗內存(因爲要重複分層存節點),但是作者也說了,可以調參數來降低內存消耗,和那些平衡樹結構達到差不多。

  2. A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list.
    With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.
    注:redis經查有範圍操作,這樣利用跳錶裏面的雙向鏈表,可以方便地操作。另外還有緩存區域化(cache locality)不會比平衡樹差。

  3. They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch
    (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.
    注:實現簡單。zrank操作能夠到O(log(N)).

About the Append Only durability & speed, I don’t think it is a good idea to optimize Redis at cost of more code
and more complexity for a use case that IMHO should be rare for the Redis target (fsync() at every command).
Almost no one is using this feature even with ACID SQL databases, as the performance hint is big anyway.

About threads: our experience shows that Redis is mostly I/O bound. I’m using threads to serve things from Virtual Memory.
The long term solution to exploit all the cores, assuming your link is so fast that you can saturate a single core,
is running multiple instances of Redis (no locks, almost fully scalable linearly with number of cores),
and using the “Redis Cluster” solution that I plan to develop in the future.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章