緩存穿透的問題
通常使用的緩存都是應用先檢查緩存中是否存在。如果存在牢裳,則直接返回緩存叶沛;如果不存在,則查詢數(shù)據(jù)庫灰署,將結果緩存并返回。
但是查詢的是一個不存在的數(shù)據(jù)晦墙,就會造成每一次請求都查詢DB肴茄,這樣緩存就失去了意義。
為了解決這個問題抗楔,通常使用特殊字符串標記無用的緩存拦坠,比如"EmptyCache"贪婉,然后在應用層做相應處理,解決此問題。但是這種方式顯得比較粗暴:
- 需要在反序列化過程中莫湘,將特殊字符串排除掉,同時需要將不存在的數(shù)據(jù)標記出來腰池,避免再查詢DB。
- 如果需要處理不同類型的數(shù)據(jù)的緩存穿透問題,是不是多加幾種特殊字符傳示弓。
因此讳侨,我們可以引入NullObject模式來解決此問題。它的作用在于提供一個對象給指定的類型奏属,用以代替這個對象為空的情況跨跨。
更具體的信息,可以參考此鏈接:https://en.wikipedia.org/wiki/Null_object
新的方案:以券(Ticket)為例
步驟一囱皿、引入抽象類AbstractNullObject
public abstract class AbstractNullObject{
private Integer id;
public void markNullObject(){
this.id = -1;
}
public void isNullObject(){
return id!=null && id<0;
}
}
一般在設計數(shù)據(jù)庫表時勇婴,都有id字段,而且id字段是自增的整數(shù)嘱腥。我們可以復用此id字段耕渴,并約定一個規(guī)則:id小0的對象,都是NullObject橱脸。
步驟二、讓Ticket繼承能夠AbstractNullObject,并添加createNullObject函數(shù)
public class Ticket extends AbstractNullObject{
public static Ticket createNullObject(String ticketCode){
Ticket ticket =new Ticket();
ticket.setTicketCode(ticketCode):
ticket.markNullObject();
}
}
步驟三分苇、更新緩存的查詢流程
舊流程 | 新流程 |
---|---|
1. 根據(jù)TicketCodes慰技,查詢緩存,并將結果加入結果集组砚。 2. 根據(jù)TicketCodes和結果集吻商,找出不存在的TicketCode,然后到db查詢,將結果加入結果集糟红,并寫入緩存艾帐。 3. 返回結果集合。 |
1. 根據(jù)TicketCodes盆偿,查詢緩存柒爸,并將結果加入結果集。 2. 根據(jù)TicketCodes和結果集事扭,找出不存在的TicketCode,然后到db查詢捎稚,將結果加入結果集,并寫入緩存求橄。 3. 根據(jù)TicketCodes和結果集今野,再次不存在的TicketCode,為這些code創(chuàng)建NullObject,并寫入緩存罐农。 4. 過濾結果集条霜,剔除NullObject。 5. 返回結果集涵亏。 |
對比新老流程宰睡,新流程增加了步驟3蒲凶、4。
步驟3的作用:為不存在的TicketCode拆内,創(chuàng)建NullObject旋圆,寫入緩存。下次再次查詢時麸恍,緩存就有數(shù)據(jù)灵巧,DB也不會查詢。
步驟4的作用:NullObject是可以正常被反序列化或南,但對調(diào)用方來說是無效的數(shù)據(jù)孩等,需要剔除。
總結
帶來的好處:
- 如果想讓Sku采够,Product避免緩存穿透的問題肄方,只要繼承NullObject接口,就能復用已有的成果蹬癌。
- 在redis反序列化過程权她,沒有特殊處理。
- 在原有的查詢流程追加行為逝薪,沒有破壞性的改造隅要,成本低。
與老方案的差異:
序列化NullObject產(chǎn)生的字符串董济,攜帶的信息量遠大于特殊字符傳步清,而且反序列化后,可以判斷是否為NullObject虏肾。