最近須要實現一個功能,關於頁面廣告自動配置的,如支付寶的支付完成頁。這篇隨筆是記錄對這個需求從分析到實現以及優化的過程,以避免之後忘記。前端
某些頁面須要配置廣告或活動宣傳圖,廣告或活動需知足隨時上下線、過時自動下線及到時自動上線。數據庫
如:如今時間2019-2-22 16:16:13,要在支付完成頁面配置領獎活動,活動要在2019-3-10 00:00:00準時上線,在2019-3-30 23:59:59結束活動。緩存
因此要的效果是,在活動上線前的任意時刻配置完活動後,頁面到時間自動上線這個活動。 也可能會是其餘的多個活動或廣告,每一個頁面廣告的個數可變,不一樣上下線時間可不一樣,其餘頁面也須要實現這樣的功能,頁面與頁面之間的活動不必定同樣。bash
需求簡單的幾句話,那麼咱們來具體的分析一下。網絡
一、【廣告或活動宣傳圖】異步
要爲不一樣頁面設置不一樣的廣告,有的頁面廣告可能同樣,也就是廣告會複用,全部要有廣告表。優化
二、【每一個頁面廣告的個數可變】【不一樣廣告上下線時間可不一樣】【頁面與頁面之間的活動不必定同樣】spa
頁面可配置多個廣告,全部要有頁面配置表,以及廣告和頁面的關係表,即頁面廣告表。code
頁面配置表主要配置頁面的廣告個數,實現【每一個頁面廣告的個數可變】,頁面廣告表主要配置頁面的每一個廣告上下線時間,實現【不一樣廣告上下線時間可不一樣】cdn
簡單分析後得出以下表結構:廣告表adv,頁面配置表page_config,頁面廣告表page_adv
這些頁面配置的廣告在一段時間內是不會變的,若是頁面請求次數較多,廣告查詢次數就會很頻繁,對數據庫形成沒必要要的壓力。因此能夠引入緩存,下降數據庫請求次數,緩解數據庫壓力。這裏使用的Redis。
什麼時候入緩存?
能夠選擇在服務啓動時異步把已在上下線時間區間內的廣告先加載至緩存,或選擇在請求時取緩存,緩存內沒有時再查庫而後放緩存。緩存時間視狀況而定。
這裏選擇的是,項目啓動時異步把符合條件的頁面廣告配置信息存入Redis,那些還沒到指定時間的先不放Redis,等到訪問頁面加載廣告時,先查Redis,若無則按條件(>=nowtime)查庫,查到後存Redis。
在接口中拿到廣告配置信息後,判斷當前時間是否在配置的時間區間內,因爲一個頁面配置多個廣告,不一樣廣告時間也不一樣,因此要迭代,把符合的返回,有過時的就作標記,而後把整個頁面的配置信息在Redis裏刪除。 (或者不選擇在啓動時加載,就在用戶請求時加入緩存,可是下面的第1步的方法在刷新加載時會用到,故不能刪)
a、查詢全部pageId
SELECT pageId FROM page_config page_adv WHERE nowtime<=endtime AND GROUP BY pageId
複製代碼
兩個表內鏈接,得List<pageId>,獲得的都是配置的有廣告的而且廣告還沒過時的pageId。
b、查詢pegeId對應的廣告圖片及跳轉連接
SELECT 字段名 FROM page_adv adv WHERE begintime<=nowtime<=endtime AND pageId={#pageId}
複製代碼
而後把查到的配置信息List<adv>(爲空時不作操做),以pageId爲KEY放入緩存。
按標準的控制層,業務層,數據訪問層寫,第一步中的邏輯就是在業務層完成的。
控制層:
控制層接參pageId,調用業務層查詢對應頁面配置的廣告信息,判空,直接返回狀態碼0,即無廣告前端不展現。
不爲空就根據業務邏輯處理數據(如img的URL加域名),而後返回狀態碼1,前端展現廣告。 這裏控制層還能夠加邏輯,迭代廣告list,把當前時間在廣告起始時間內的返回,不在的不返回,而且只要有一個廣告過時,就把這個頁面的廣告list緩存清掉。這個邏輯是把過時的清掉。
業務層:
先取緩存,沒有再查庫判斷不爲空(本頁面配置的有廣告),放入緩存(pageId爲KEY),而後返回。
數據訪問層:
SQL:
SELECT 字段名 FROM page_config adv page_adv WHERE begintime<=nowtime<=endtime AND pageId=#{pageId}
複製代碼
三表聯查,根據pageId查詢當前頁面配置的廣告活動信息(已在廣告活動時間內)
爲何使用刷新加載?
由於有這樣的場景:給頁面A配置了一個廣告(當前時間在廣告的起始時間內),那麼這個頁面的廣告已經在緩存裏了,假如此時A頁面要新加一個廣告,在後臺配置後若是不作其餘操做,這個廣告不會顯示(假設緩存時間較長,爲一天),由於庫更新了,緩存沒有同步更新。
解決方案
使用Redis的發佈訂閱機制實現緩存的刷新加載,使新配置的廣告及時可以顯示。 刷新加載的回調方法即第1步中的方法。
想想,目前的實現存在什麼問題?
存在的問題
假若有頁面須要配置廣告,可是尚未配(前端已經開發完上線,每次都會調接口查廣告信息),那麼數據庫確定查不到,緩存也沒有。若是這個頁面訪問量很大,那麼緩存沒命中就查庫,這樣對庫的壓力就會很大,這就是緩存穿透,請求上來了很容易擊垮數據庫。那怎麼辦呢?
解決方案
當頁面沒有配置廣告時,在緩存存標誌,查詢時先看標誌,在決定是否往下走。
具體方案
這時,上面的第1步就要改了。
一、首先改第1步的步驟a的SQL,把全部的pageId都查詢出來。
使用左鏈接
SELECT pageId FROM page_config LEFT JOIN page_adv ON ... GROUP BY pageId
複製代碼
或者乾脆查page_config
SELECT pageId FROM page_config
複製代碼
目的是把已在page_config表中配置,但關係表中page_adv未配置廣告的pageId也查出來,這樣才能給未配置廣告的pageId在緩存裏放標誌
二、第1步的步驟b的SQL改成
SELECT 字段名 FROM page_adv adv WHERE nowtime<=endtime AND pageId={#pageId}
複製代碼
而後把查到的配置信息放入緩存以前判斷【爲空時的不作操做】改成【爲空時存入一個標誌】假如這個標誌KEY爲pageId+"_EMPTY_FLAG",value爲"DB_IS_NULL"
爲何只判斷小於結束時間
由於若是該頁面配置的廣告開始時間大於當前時間,那麼這個是查不到的,會被處理爲DATABASE_IS_NULL,若是在這個標誌還沒失效以前就到了配置的開始時間了,那麼這個廣告不會被展現。全部要讓未到開始時間的也放入緩存,而後讓控制層去判斷在不在時間區間。
三、因此要在第2步也修改一下
在業務層裏取緩存中的廣告列表以前,先從緩存取pageId+"EMPTY_FLAG"的value判斷爲"DB_IS_NULL"直接返回空,這樣就能解決緩存穿透的問題了。
繼續修改第2步的業務層,查庫的SQL一樣要改:
SELECT 字段名 FROM page_config adv page_adv WHERE nowtime<=endtime AND pageId=#{pageId}
複製代碼
而後判斷爲空的話,同上面的黃字那樣處理。
四、最後,第3步的刷新加載調的是第1步的方法,不用改。
固然這個緩存穿透的優化方案只是其中一種。還能夠這樣:
一、控制層攔截:根據pageId查詢page_adv表,查不到說明沒配置,直接返回。
二、page_config 表增長字段,表示當前頁面已經配置的廣告個數,默認0,每配置一個該字段加1,把大於0的pageId緩存起來,調接口時前判斷在不在緩存裏。
實現這個功能並非太難,主要用到了Redis的緩存技術,Redis發佈訂閱機制,關鍵就是細節的把控,以及緩存穿透的處理。
(封面圖片來源於網絡,侵權請聯繫刪除)