小結UID替換器的實現

在推薦系統中,我們常需要根據用戶的標示(UID)和訪問行為給用戶生成推薦。但某些情況下,我們需要替換用戶請求中的UID為特殊的UID,以測試推薦結果。本周基于這個需求,實現了UID替換器。

功能

首先我們需要做一個管理后臺,對我們要替換的UID或者其他ID進行管理,有如下需求:

1 增加,修改,刪除UID替換規則
2 UID規則啟用/停用
3 UID替換規則在線上生效

以上第三個需求相對難以實現一些。UID替換規則在線上生效,其潛臺詞是每次修改了替換規則之后,需要其立即在線上生效。對于UID替換規則對象,我們可以用關系數據庫,如MySQL存儲。

一般的想法是在線上請求過來的時候,依據請求中的UID查詢數據庫,看是否有相應的規則,如果有則取出替換UID進行替換,如果沒有,則略過。這個想法在請求量不大的時候沒有問題,但如果是每秒成千上萬次請求,MySQL數據庫難以抗住,就會出現問題。

這時候我們想到的是把規則數據加載到內存中,線上請求直接從內存中查找,就不需要每個請求都要查找數據庫。在管理系統中,如果對規則修改了之后,“UID替換規則在線上生效”就是把修改后的數據重新加載到內存。

這就是“UID替換規則在線上生效”這個需求的真實含義。

其次,我們的線上系統要提供兩方面的功能:

1 提供加載MySQL數據到線上內存的接口,方便管理系統調用
2 在請求過來的時候,根據請求中的UID和內存中的規則,實現UID替換

第一個功能,由線上系統暴露一個API,管理系統需要使線上生效時,就調用該API,重新由MySQL對應的規則表加載數據到內存對象中。線上API可以直接訪問MySQL數據,也可以由管理系統暴露一個服務,然后線上API調用該服務獲取生效規則數據。

第二個功能的實現要求從MySQL中加載出來的數據保存在一個全局的對象中。當然,也可以直接存儲在Tair/Redis/Memcache這樣的內存數據庫中,甚至可以做成一個服務,供其他服務調用。不過此處為了簡略,我們實現的版本就是直接放在一個全局的Java的HashMap對象中。然后對線上的Servlet做一個Filter,在Filter中使用全局對象提供的規則數據實現替換邏輯。

在本處我們使用了HashMap作為規則存儲對象,不用擔心線程安全的問題。HashMap并發讀沒有問題,并發寫會出現問題。但在我們的場景中,寫數據場景有二:線上服務啟動時,要加載數據到HashMap;我們刷新時,要重新加載數據到HashMap。這兩個場景都是單線程去寫,所以HashMap堪堪夠用,如果實在不放心,可以改用concurrentHashMap。

設計

設計主要有兩個方面,一個是數據庫表的設計,另一個是HashMap的設計。前者可以這么做:

---- table idmapper
id int 規則ID 自增
name varchar 規則名
source_id varchar 源ID
direct_id varchar 目的ID
enable int 是否啟用 0:不啟用,1:啟用

除了常見的對字段的增刪改查功能之外,還需要向外暴露已經啟用的規則,查詢語句如下:

select * from idmapper where enable=1

可在Web框架中,如Spring MVC,把這個查詢的結果以API的方式暴露,訪問API可以獲得對應的Json數據。

第二個是HashMap的設計,因為在文章里面描述的場景相對簡單,而實際上我們有更復雜的需求,如不止替換UID這樣的參數,還可以替換別的參數,還有一些場景過濾,如只在特定場景下并且有對應規則才實現替換。這些功能使得HashMap的設計相對復雜,此處只需要將sourceID為key,directID為value。

第三個是要設計一個單例。單例中包含一個靜態的HashMap對象,以及加載數據到HashMap對象的函數和根據UID查找替換UID的函數。實際上我們的系統相對復雜很多,提供有專門的數據服務接口,需要按照該接口進行開發。此處為了方便,我們簡單就設計一個單例就好。偽代碼大概如下:


public class MapRuler {

    private static HashMap ruler = new HashMap();
    // 私有構造函數
    private MapRuler() {}
    
    // 此處使用餓漢式即可
    private static MapRuler mapRuler = new MapRuler();

    public void loadEnableRuler() {
            ruler = ... // 調用管理系統提供的API,將Json數據解析成HashMap
    }

    public String getDirectId(String sourceId) {

            if(ruler.containsKey(sourceId)) {
                    return ruler.get(sourceId);
            }
            // 沒有匹配的規則返回null
            return null;
   }
 
 public static MapRuler getInstance() {
            return mapRuler;
    }

}

單例使用餓漢式就可以,因為規則數據在線上應用啟動時就要加載到內存對象中,這時候就要存在MapRuler對象。這個場景并不需要延遲加載這種功能。

實現

在實現過程中,我遇到不少問題,踩了不少坑。一方面是我很久沒寫前端了,目前的管理系統是之前留下來的,數據都用ajax請求后端API,然后前端寫js把數據組裝呈現。整個前端代碼中JS/CSS/HTML混雜在一起,讓人感覺很不好。如果要讓我開發這個管理系統,我肯定不會這么做。

  1. css/js/html要盡可能做到分離開來,css放在專用的文件中,js的函數盡可能做到復用,不能每個頁面都寫一套js函數(這個項目幾乎是的)。
  1. 現有的一些前端框架如vue/anglar/react都能幫助省很多功夫。這個系統中只使用了angular的路由功能,其他是jQuery和原生js拼接。
  2. 不會考慮只用cookie標記用戶身份,居然還是明文的用戶名,我也是醉了。其實內部偽造一下這個cookie,管理系統的權限如同虛設。像這種后端只負責提供API,并由前端呈現的應用,有專門的權限方案,例如授權機制,或者token令牌等。
  3. 代碼里面不使用eval函數執行js函數。eval本身就不夠安全,很多語言中都不怎么建議使用這個函數,js也是一樣。

這種前后端分離的做法優點很多,其一后端減輕了重擔,只需要提供API接口。而前端相應的任務就重了些,不過功能實現會更加靈活。我們的后端是用Spring MVC寫的,實現對規則的增刪改查的API也很簡單。但是在前端實現上踩了不少坑。

例如用jQuery取id的數據時,如果前面忘了加#,然后就取不到數據。另外,原有的管理系統的代碼有一個比較坑爹的BUG。在實現修改功能的時候,onclick對應的方法傳進了很多參數,那些參數是通過js用字符串拼接的,然后用eval執行。這造成某個參數是json字符串,或者里面含有單引號的時候,該修改按鈕點擊無效。我嘗試了好幾個辦法,都沒有效果。但后來想到其實可以把帶有單引號的字符串先用Base64加密,去掉單引號,然后作為參數傳入onclick對應的方法,在onclick對應的方法里面用Base64解密,這樣問題才解決。其實一個比較好的辦法是,在onclick對應的函數參數中,只傳入id,函數實現中用ajax從后端獲取數據。

有一個比較好的工具可能幫助省了很多前端問題。chrome的開發者工具,真是神器,用好了這個東西,前端問題基本搞定了一半。不會的,不確定的代碼都可以拿到控制臺運行一下,看看結果,再寫到生產環境的代碼中。

chrome控制臺

另一方面是我對線上API還不夠熟悉,也跟不熟悉aone發布系統有關,畢竟這個東西也是剛用起來。推薦API中會對請求用一個Filter進行包裝,包裝成我們所需要的用于推薦的Request。我最開始走了彎路,把UID替換放在了該Filter之前,那時候我們只能通過getParameter()取得UID參數,但修改了后是沒法用setParameter()函數改回去的,因為沒有這個函數。正確的做法是把替換的Filter放在包裝Filter之后,根據我們自己定義的Request對象替換掉里面的UID。還值得一說的是,替換Filter的配置要在包裝Filter之后,這樣獲得的Request對象才正常而不是一個null,畢竟只有先把原request轉換成推薦Request,我們才能用上推薦Request是不是?

總結

說起來這并不是一個很難實現的東西,但借助這次的實現,重新寫了一把js,學了點Spring MVC,以及了解了線上API的東西。當我在預發機器上測試并打印出替換成功的結果時,感覺還是很開心的。但是我也知道,要學的東西還有很多。接下來可能找時間評估一下把后臺管理系統重寫的任務量,前端部分考慮用vue或者其他框架,優化一些使用功能。

雖然目標是一個數據工程師,但怎么也得會Web應用開發吧。So,走起來。

    雄關漫道真如鐵,而今漫步從頭越。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,677評論 25 708
  • (六) 五年前。 那天晚上是上元節,整個長安城舉城歡慶,江寒浸也從冷林閣出來。 東風夜放花千樹,更吹...
    酥小鹽閱讀 364評論 0 0
  • 今天練習 木有哭... 我說的最過分的詞 是兩次滾 在她第一百次小跑上場的時候 朱先生窩在沙發刷手機 聽見我吼他閨...
    Tt_80dc閱讀 228評論 0 0
  • 雙十一過后的第二天,王先森發來微信問:你零花錢還剩多少? 我回答:今天11月12號。 王先森:怎么了? 我回答:馬...
    蘇素的異想時空閱讀 801評論 0 0
  • 經常在電視劇里面看到犯罪嫌疑人指認的環節,以及目擊者詢問的環節,一般目擊者都會詳細的,知無不言的還原作為目擊者看到...
    十方迦南閱讀 936評論 0 1