如何實現頁面廣告隨時上下線、過期自動下線及到時自動上線

walkinger發表於2019-03-05

背景引入

最近需要實現一個功能,關於頁面廣告自動配置的,如支付寶的支付完成頁。這篇隨筆是記錄對這個需求從分析到實現以及優化的過程,以免以後忘記。

需求描述

某些頁面需要配置廣告或活動宣傳圖,廣告或活動需滿足隨時上下線、過期自動下線及到時自動上線。

如:現在時間2019-2-22 16:16:13,要在支付完成頁面配置領獎活動,活動要在2019-3-10 00:00:00準時上線,在2019-3-30 23:59:59結束活動。

所以要的效果是,在活動上線前的任意時刻配置完活動後,頁面到時間自動上線這個活動。 也可能會是其他的多個活動或廣告,每個頁面廣告的個數可變,不同上下線時間可不同,其他頁面也需要實現這樣的功能,頁面與頁面之間的活動不一定一樣。

需求分析

需求簡單的幾句話,那麼我們來具體的分析一下。

提取關鍵詞

  • 廣告或活動宣傳圖
  • 隨時上下線、過期自動下線及到時自動上線
  • 每個頁面廣告的個數可變
  • 不同廣告上下線時間可不同
  • 頁面與頁面之間的活動不一定一樣

資料庫分析

1、【廣告或活動宣傳圖】

要為不同頁面設定不同的廣告,有的頁面廣告可能一樣,也就是廣告會複用,所有要有廣告表

2、【每個頁面廣告的個數可變】【不同廣告上下線時間可不同】【頁面與頁面之間的活動不一定一樣】

頁面可配置多個廣告,所有要有頁面配置表,以及廣告和頁面的關係表,即頁面廣告表

頁面配置表主要配置頁面的廣告個數,實現【每個頁面廣告的個數可變】,頁面廣告表主要配置頁面的每個廣告上下線時間,實現【不同廣告上下線時間可不同】

簡單分析後得出如下表結構:廣告表adv,頁面配置表page_config,頁面廣告表page_adv

表結構

思考

這些頁面配置的廣告在一段時間內是不會變的,如果頁面請求次數較多,廣告查詢次數就會很頻繁,對資料庫造成不必要的壓力。所以可以引入快取,降低資料庫請求次數,緩解資料庫壓力。這裡使用的Redis。

何時入快取?

可以選擇在服務啟動時非同步把已在上下線時間區間內的廣告先載入至快取,或選擇在請求時取快取,快取內沒有時再查庫然後放快取。快取時間視情況而定。

這裡選擇的是,專案啟動時非同步把符合條件的頁面廣告配置資訊存入Redis,那些還沒到指定時間的先不放Redis,等到訪問頁面載入廣告時,先查Redis,若無則按條件(>=nowtime)查庫,查到後存Redis。

在介面中拿到廣告配置資訊後,判斷當前時間是否在配置的時間區間內,由於一個頁面配置多個廣告,不同廣告時間也不同,所以要迭代,把符合的返回,有過期的就做標記,然後把整個頁面的配置資訊在Redis裡刪除。 (或者不選擇在啟動時載入,就在使用者請求時加入快取,但是下面的第1步的方法在重新整理載入時會用到,故不能刪)

具體實現

第1步、專案啟動時先把頁面廣告配置資訊存入Redis

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放入快取。

第2步、給前端寫介面查詢頁面廣告

按標準的控制層,業務層,資料訪問層寫,第一步中的邏輯就是在業務層完成的。

控制層:

控制層接參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查詢當前頁面配置的廣告活動資訊(已在廣告活動時間內)

第3步、重新整理載入

為什麼使用重新整理載入?

因為有這樣的場景:給頁面A配置了一個廣告(當前時間在廣告的起始時間內),那麼這個頁面的廣告已經在快取裡了,假如此時A頁面要新加一個廣告,在後臺配置後如果不做其他操作,這個廣告不會顯示(假設快取時間較長,為一天),因為庫更新了,快取沒有同步更新。

解決方案

使用Redis的釋出訂閱機制實現快取的重新整理載入,使新配置的廣告及時能夠顯示。 重新整理載入的回撥方法即第1步中的方法。

進一步優化

想一想,目前的實現存在什麼問題?

存在的問題

假如有頁面需要配置廣告,但是還沒有配(前端已經開發完上線,每次都會調介面查廣告資訊),那麼資料庫肯定查不到,快取也沒有。如果這個頁面訪問量很大,那麼快取沒命中就查庫,這樣對庫的壓力就會很大,這就是快取穿透,請求上來了很容易擊垮資料庫。那怎麼辦呢?

解決方案

當頁面沒有配置廣告時,在快取存標誌,查詢時先看標誌,在決定是否往下走。

具體方案

這時,上面的第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在快取裡放標誌

2、第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,如果在這個標誌還沒失效之前就到了配置的開始時間了,那麼這個廣告不會被展示。所有要讓未到開始時間的也放入快取,然後讓控制層去判斷在不在時間區間。

3、所以要在第2步也修改一下

在業務層裡取快取中的廣告列表之前,先從快取取pageId+"EMPTY_FLAG"的value判斷為"DB_IS_NULL"直接返回空,這樣就能解決快取穿透的問題了。

繼續修改第2步的業務層,查庫的SQL同樣要改:

SELECT 欄位名 FROM page_config adv page_adv WHERE nowtime<=endtime AND pageId=#{pageId} 
複製程式碼

然後判斷為空的話,同上面的黃字那樣處理。

4、最後,第3步的重新整理載入調的是第1步的方法,不用改。


當然這個快取穿透的優化方案只是其中一種。還可以這樣:

1、控制層攔截:根據pageId查詢page_adv表,查不到說明沒配置,直接返回。

2、page_config 表增加欄位,表示當前頁面已經配置的廣告個數,預設0,每配置一個該欄位加1,把大於0的pageId快取起來,調介面時前判斷在不在快取裡。

總結:

實現這個功能並不是太難,主要用到了Redis的快取技術,Redis釋出訂閱機制,關鍵就是細節的把控,以及快取穿透的處理。





(封面圖片來源於網路,侵權請聯絡刪除)

相關文章