在快取能為應用程式提供資料之前,系統必須先能將資料從資料來源填入快取之中。那麼該如何將資料填入快取之中呢?

Client Pull的模式
最簡單的模式,便是由使用資料的應用程式端(以下簡稱為客戶端)來發動。

在這個模式下,當客戶端想要存取資料時,便至快取中檢視資料是否存在,倘若快取中存在資料,則直接取得資料,毋需至來源端重新取得。但若快取中並不存在資料,那麼則至資料來源端取得資料,並置於快取之中,以供接下來的存取動作使用。當然,若快取中資料過期,則由快取機制自行清除。我們可以把這種模式稱為Client Pull,因為是由使用資料的客戶端自行至資料來源端,將資料「拉」到快取中的。

Client Pull的模式,是最簡單也是最普遍建立快取中資料的模式。在這個模式下,可能會衍生一些問題,例如,快取中的資料可能不夠即時。如果只是單純的Client Pull時,當資料來源中的資料有所異動時,並不會影響到快取中資料的更新,因此,就會造成快取中資料不夠即時的情況。

快取中資料不夠即時,不一定是個問題。對有些應用來說,是允許一定程度的資料不即時。例如,Facebook塗鴨牆上的資訊,就可以允許不那麼即時,畢竟,對用戶來說,並沒有要求那麼嚴格的即時性。相反的,若是一個股票操作軟體,其中的股票即時報價資料,就必須要有很高的即時性,而不能接受快取中資料與原始資料不夠同步的情況。

如果你的應用可以接受不即時的情況,那麼這不構成問題,但是,如果需要很高的即時性,就不能使用如此單純的Client Pull模式了。

除此之外,單純的Client Pull模式有另一個問題,就是可能存在一個競速的情況(Race Condition)。什麼時候會發生呢?

當有許多客戶端程式同時嘗試存取快取中的同一份資料,但是這一份資料目前不在快取中(因為尚未載入或已經過期)時,就會同時引發多個嘗試自資料來源載入資料至快取的動作。

而這些資料會被設計置放到快取中,往往是因為,這些資料自資料來源(例如資料庫)中載入計算的時間太長,而同時多個嘗試載入的動作,變成使得資料來源的負荷在那同時載入的瞬間變得超重,因而拖垮了資料來源的運行效能。

利用專職程式持續將資料載入快取
倘若只想解決這個因為Race Condition而造成的問題,可以考慮使用第二種載入資料至快取的模式,也就是由另一個專職的程式,負責將資料來源中的資料,在資料過期前先行載入至快取中,這麼一來,快取中永遠都保持有可用的資料,也就不會發生眾多的Client同時發現快取中沒有資料,而同時觸發載入資料的動作。

所以,這個專職的程式可以是一個獨立的程式,或是應用程式中一個獨立的執行緒,它在系統初始化時,會先將所有快取中應有的資料,統統載入至快取中。接著,它會以略低於快取中資料更新週期的週期重新至資料來源中取得資料,並更新至快取中。因為實際更新的週期短於快取的更新週期,所以,在客戶端程式存取到過期的快取資料前,它已經先行主動的更新資料了,永遠也不會發生同時嘗試重新載入資料的動作。

不過,雖然這種由專職的程式主動更新資料的模式,可以解決因為Race Condition而造成的問題,卻無法解決資料即時性的問題。

Server Push模式
有一種方式有可能可以處理資料即時性的問題,也就是,如果資料來源的更新動作,都是由客戶端程式來進行時,那麼每次的更新動作,客戶端程式都應該很清楚,更新動作究竟會影響到那些快取中資料的即時性,所以,每次的更新動作會伴隨著作廢掉快取中對應的資料,並且重新加以載入。這麼一來,每當後端資料源中的資料有所異動時,便會同步更新快取中對應的資料,快取中的資料也就可以跟著改變至最新的內容,自然也就解決了即時性的問題。

這個作法當然可以解決即時性的問題,不過在快取資料的更新動作上,就會變得複雜許多。

因為,客戶端程式在設計資料的更新操作時,必須考慮到每個更新動入,究竟會影響到那些在快取中的資料,進而連帶進行這些資料在快取中的更新動作。也就是說,你不容易建構一個解決方案能自動化的進行快取中對應資料的更新,所有的細節,都必須倚賴程式設計者自身的自覺及行動才能辦到。一旦疏忽了某個部份,那麼該部份就會在即時性上發生問題。這是這一個方法的問題所在。

而在快取資料的更新上,還有第三種模式,也就是所謂的Server Push模式。在這種模式底下,Client並不主動到資料來源端拉取資料,而是由資料來源端主動將資料「推」送到快取中。

所以,當資料來源端的資料有所異動時,資料來源端會主動偵測到此事,接著便可以立即將對應的資料推送到相對的快取中。在這種模式下,快取資料的過期與否、是否需要更新,就可完全由資料來源端決定,而毋需交由客戶端程式碼決定,而資料來源端是最清楚資料是否有所異動的角色。採取此種資料更新的模式,便同時解決了即時性以及上述所提及Race Condition的情況。

這種Server Push的模式,具體上要如何實作呢?例如有人以MySQL為資料庫、搭配memcacehd做為快取伺服器,利用MySQL的Trigger來偵測資料庫中資料的更新,接著撰寫Trigger程式,當Trigger所設定的資料有所異動時,便會執行Trigger程式,而在Trigger程式中則整合memcached,將異動的資料送至memcached中。

這麼一來,客戶端程式使用快取資料的方式就獨立於快取資料的更新之外,毋需考慮到快取中資料是如何被載入、是否足夠即時、有沒有同步被載入的問題、等等。因為一切的更新邏輯全部都被移到資料來源端,也就是資料庫了。

不過,雖然Server Push的模式,簡化了客戶端程式碼的開發,但是,其實骨子裡只是將複雜度轉移至資料來源端。原先是由客戶端程式碼處理資料在來源端及快取中的對應關係,改由資料來源端來進行對應。

從系統的整體架構面來說,由Server Push讓客戶端程式碼,幾乎可以說是隔離在「快取」這個概念之外;對客戶端程式碼而言,要讀取的資料,就像是天生就放在一塊可高速讀取的區域,需要的時候,儘管去讀就好了,根本不需要理會資料究竟是怎麼來,因為來源端會在需要的時候,主動將資料推送至這塊區域裡。而應用程式在需要更新資料時,也只需要直接面對資料庫進行更新的動作即可,毋需理會所更新的資料究竟又會如何被同步至這塊區域中。可以說,大大簡化了程式設計時的模式。

不過,這樣的作法形成增加了資料來源端的負荷,尤其當大多數時候,資料來源端都是關聯式資料庫,而在一些系統架構裡,資料庫又是集中式的資源時,那麼,增加資料庫的負荷,反而有礙於系統提升規模的能力。再者,這麼一來,也形成將系統的事務邏輯,除了應用程式中的程式碼外,又多擴散到資料庫的Trigger程式,也會提高部署應用程式時的複雜度。

因此,這相關的優缺點,便構成了設計者在選擇方案時必須要考量的。

作者簡介


Advertisement

更多 iThome相關內容