準備加班中ing.....數據庫
需求要點
每一個用戶都有本身的我的空間,當有其餘用戶來訪問的時候,須要添加訪客記錄,而且更新爲最新的訪客,這裏設計到一個坑,若是存在這個用戶的訪問記錄須要更新用戶的最後訪問時間。那這個需求在技術維度來講,有什麼特色嗎? 先想10秒鐘,在接着往下看!!!數組
有什麼設計要點呢?緩存
用戶的訪客記錄必定要緩存,要否則怎麼抗住大併發呢?
因爲最新的訪客記錄變化很是快,要有一種能快速添加新數據,刪除老數據的數據結構。
緩存的篇章今日暫且不說,說一下以上的第二點,也就引出了今日數據結構主角:鏈表bash
鏈表百科:鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。鏈表屬於線性結構。網絡
鏈表分類
單鏈表:鏈表中的元素的指向只能指向鏈表中的下一個元素或者爲空,元素之間不能相互指向。也就是一種線性鏈表。
public class Node<T>
{
//當前節點的數據元素
public T Data { get; set ; }
//當前節點的下一個元素
public Node<T> NextNode { get; set ; }
}
複製代碼
雙向鏈表:每一個鏈表元素既有指向下一個元素的指針,又有指向前一個元素的指針,其中每一個結點都有兩種指針。
public class Node<T>
{
//當前節點的前一個節點
public Node<T> PreNode { get; set ; }
//當前節點的數據元素
public T Data { get; set ; }
//當前節點的下一個元素
public Node<T> NextNode { get; set ; }
}
複製代碼
循環鏈表:指的是在單向鏈表和雙向鏈表的基礎上,將兩種鏈表的最後一個結點指向第一個結點從而實現循環。
特性
元素的數量能夠隨時擴充。因爲鏈表在物理的存儲單元上是非連續的,這就早就了它天生的優點,個人節點能夠在任意符合要求的地方分配內存。
添加元素: 單鏈表:當在一個位置N以後插入新元素的時候,單鏈表首先把當前位置N的元素的Next指針指向新的元素,而後新的元素的Next指針指向N+1位置的元素。固然若是是在首位置插入新元素,只須要把新元素的Next指針指向鏈表的首元素便可,同理,若是要在單鏈表尾部插入新元素,只須要把單鏈表的尾部元素的Next指針指向新元素。至於循環單鏈表,無所謂首元素和尾元素之分。
雙向鏈表: 在位置N以後添加新元素和單鏈表原理相似,原理也是修改元素的指針指向。可是這裏有一個不一樣,雙向鏈表要修改先後元素(N位置和N+1位置)和新元素三個Node的指針,因此略微麻煩一點。
刪除元素: 單鏈表:當要刪除位置N的元素的時候,只須要把N-1位置元素的Next指針指向N+1便可。
雙向鏈表:當要刪除位置N的元素的時候,須要修改N-1位置元素的Next指針指向N+1元素,同時還要修改N+1位置元素的Pre指針指向N-1元素。
查找元素: 因爲鏈表的元素在內存中並不是連續,因此不能像數組那樣擁有O(1)的查找時間複雜度,只能是經過首元素去遍歷鏈表,因此時間複雜度爲O(n)
程序設計
給你10秒回到X總的需求中來。經過對鏈表的介紹,咱們該選擇哪一種鏈表呢?這裏我先說一下個人思路,若有錯誤請指正:數據結構
當一個訪客進入我的空間的首頁時,大多數狀況下,訪客記錄只須要緩存前100條或者200條便可,也就是說這個場景是存在熱點數據的,80%(甚至更高)的請求命中在最近100條訪客數據上,不多人會去查看好久之前的記錄。因此基於佔用內存空間上的考慮,我決定緩存最近的100條訪客數據。
假設我用鏈表緩存了前100條數據,其中在非首位置有一條訪客A的記錄,此時A又訪問的這個用戶空間,我須要把A的記錄移到首位置,這個過程經歷了刪除A數據,在首位置添加A數據。假如A開始的位置是N,我在刪除N位置數據的時候,須要查找N-1的位置元素修改其指針指向,若是是單鏈表因爲當前位置N的元素中沒有N-1位置元素的信息,全部須要從新遍歷鏈表。若是是雙向鏈表呢,位置N的元素中保存了位置N-1的元素,因此沒有必要在從新遍歷鏈表了,這也是雙向鏈表對比單鏈表的優點,雖然內存佔用上多了一個指針的內存大小,可是在實際的應用場景中更爲經常使用。因此我選擇雙向鏈表。刪除操做和添加操做時間複雜度都是O(1).
對同一個空間的訪問,必然存在鎖和多線程的問題。因此我在選擇框架的時候優先選擇了基於Actor模型的框架。避免了在同一個用戶空間上加鎖的操做。
因爲基於Actor模型的框架,因此我沒有采用相似Redis這樣的進程外緩存,而是採用了進程內緩存,畢竟網絡傳輸的速度再快也比內存操做要慢的多。應用層的Actor服務自然支持分佈式。若是對actor 不太瞭解的同窗能夠度娘一下。
優化
閱讀到這裏你是否感受哪裏有問題呢?是的,就是鏈表元素的查找,因爲只能是遍歷,全部鏈表查找元素的時間複雜度爲O(n),那有沒有辦法優化呢?那就是咱們之後要講的另一種數據結構了。
空間的訪客記錄是以時間爲維度的倒序排列,因此業務以及DB時間列的設計類型推薦爲UTC時間戳long類型,畢竟long類型在多數語言中比datetime類型佔用內存要小不少。
不管是否使用緩存,用戶的訪問記錄都是須要DB來持久化的,當有大量的請求的時候,咱們能夠利用某種機制來批量持久化到DB,而不是一個請求就訪問數據庫一次。
當對空間的訪客記錄實時性要求不是很高的時候,咱們能夠每10秒或者5秒更新緩存,也就是批量更新緩存,這比單條加鎖更新緩存效果更好。
X總的我的空間需求並無結束,菜菜仍然在持續優化中,歡迎大佬指正!多線程
添加關注,查看更精美版本,收穫更多精彩 併發