• 0

    用戶事件的存儲與分析

    jopen 9年前發布 | 18K 次閱讀 存儲

    許多時候我們說一款產品的設計是數據驅動的,是指許多產品方面的決策都是把用戶行為量化后得出的。一個典例的例子就是注冊流程的設計,如果用戶需要填寫的注冊信息較多,一般就會分成多個頁面,而產品設計師最關心的就是每個頁面的流失率,從而不斷的對這個流程作調整以達到信息量與流失率之間的平衡。

    為了能夠量化用戶的行為,前提是要將各種用戶事件都保存下來。其中最典型的事件包括user creation, page view和button click,但實際上還有許多其他事件,比如用戶更改了狀態或是錄入了某些數據等等。目前有許多第三方的服務可以幫助你做這方面的統計,國內有友盟,國外有Google Analytics和Mixpanel。但如果你記錄的事件數量非常龐大,或是對之后的數據分析有非常定制化的要求,那就要考慮自己構建事件分析的平臺,而這個過程中最關鍵的一步就是如何存儲用戶事件。

    首先我們來分析一下用戶事件存儲有哪些特性

    • 數據量巨大 用戶在應用中產生的事件數量遠遠大于他們產生的數據。非常簡單的一個例子,就是用戶在瀏覽各個頁面時,他們并不產生任何數據,但卻產生了大量的page view事件。所以事件數據的量往往是主數據庫的幾十倍甚至上百倍。
    • 不一致的數據結構 雖然所有的事件都有一些公共的屬性,比如事件名稱,事件時間,應用的版本與操作系統等等,但有很多事件有自己特定的屬性,比如用戶注冊事件,我們會非常關心注冊的渠道,是用email注冊還是用社交網絡注冊(比如微博,微信等),同樣一個論壇貼子的查看事件,我們會想要記錄貼子的ID與版塊的ID。這種不一致性,給我們設計數據存儲結構帶來了許多麻煩。
    • 聚合式查詢 我們在使用用戶事件的數據時,往往不關心單個人的事件,而只關心統計結果。所以一個典型的查詢模式就是訪問大量的歷史數據,對查詢結果按某一個特定的維度聚合。

    存儲這類數據的方法一般可以分為三類

    • 傳統關系型數據庫,如MySQL, PostgreSQL
    • Hadoop HDFS + Hive
    • 數據倉庫,如Amazon Redshift, Microsoft SQL Server for PDW

    后兩種方案有先天的技術優勢,但維護成本高,并且其優勢需要在數據量突破某個臨界點之后才能真正顯現。第一種方案看似毫無亮點,但對于創業型小團隊來說,卻有其價值在。因為關系數據庫大家都很熟悉,對于運維來說,沒有額外的維護成本。當數據量在TB以下時,如果正確地建立索引,查詢速度也是非常快,并且也可以通過Sharding的方法做分布式的擴容。Glow目前正處于從MySQL存儲到Redshift的轉型,所以今天我們主要想分享一下用關系數據庫來存儲與分析用戶事件的一些經驗,我們會在將來的博客中介紹后兩種系統(它們往往是共存的)

    表結構的設計

    第一個要解決的問題是,我們應該將所有的事件存在單一表中,還是每個事件存在單獨的表里。兩者有其各自的優勢。比如后者,每個事件單獨建表,表結構非常清晰,易于理解。但缺點是每次定義新事件都需要改動數據庫結構。我們希望事件的定義是非常輕量的,所以在Glow我們選擇了前者。前者的關鍵問題是,各種事件都有不同的屬性集合,難道把所有事件的所有屬性都放在表結構的定義中?這樣很快這個事件就會有成百上千的字段,對于存儲與查詢來說都非常的低效。我們的做法是定義一組通用的字段用于事件屬性,并在代碼中定義映射關系。

    我們的事件表結構大致是這樣的

    CREATE TABLE `EventLog` (  
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `event_time` bigint(20) NOT NULL,
      `event_name` varchar(255) NOT NULL,
      `user_id` bigint(20),
      `platform` tinyint(4),
      `app_version` varchar(20),
      `ip_address` varchar(20),
      `device_id` bigint(20),
      `data_1` bigint(20),
      `data_2` bigint(20),
      `data_3` bigint(20),
      `data_4` bigint(20),
      `data_5` bigint(20),
      `data_6` bigint(20),
      `text_1` varchar(255),
      `text_2` varchar(255),
      `text_3` varchar(255),
      `text_4` text,
      `text_5` text,
      `text_6` text,
      PRIMARY KEY (`id`),
      KEY `idx_event_id` (`event_time`, `event_id`),
      KEY `idx_event_and_platform` (`event_time`, `event_id`, `platform`),
      KEY `idx_event_and_version` (`event_time`, `event_id`, `version`),
      KEY `idx_user` (`user_id`),
    ); 

    首先,我們會記錄事件的名稱event_name與時間event_time,然后是所有事件共有的屬性user_id, platform, app_version, ip_address, device_id。隨后的data_*text_*則是用于各個事件的特有屬性。事件在代碼中的定義大致如下

    FORUM_NEW_TOPIC = {  
        'name': 'forum new topic',
        'mapping': {
            'room_id': 'data_1',
            'subject': 'text_1',
            'content': 'text_4',
        }
    }

    這是論壇中發貼事件的定義,應該很容易看懂。討論區的IDroom_id是整型,標題與貼子正文都是字符串,但正文很可能超出255長度的限制,所以被放入text_4。再看一個更有趣些的例子

    SHARE_APP = {  
        'name': 'share app',
        'mapping': {
            'channel': {'field': 'data_1', 
                        'enum': ['非死book', '推ter', 'sms', 'email']},
            'message': 'text_1',
        }
    }

    這是分享app的事件,其中分享渠道channel是一個枚舉類型,所以被映射到了data_1而不是text_1。在存儲該類事件時,我們會驗證事件中的channel的值是否為上述4個字串之一,并且只保存字符串的hash值。在從數據庫讀取該類事件時,當我們解析data_1字段的值時,會反向查找hash值對應的原始字串。在實際使用中,text_*的字段的使用率是比較低的,因為大部分的用戶事件中的字符串都是枚舉類型。枚舉型的存儲占用空間更小,查詢也更快,因為整數比較要明顯快于字串比較。

    在Glow中,有一個事件定義文件,我們稱為事件的masterfile,這個文件定義了Glow中所有的事件,由數據分析團隊管理與修改。另外有一個模塊專門負責將系統中接收到事件,根據masterfile,轉化成正確的數據格式并存入數據庫。

    性能與擴容

    之前也提到,用戶事件的數據遠多于其他的生產環境數據。當單表的數據條數過大時,無論是查詢還是插入性能都會下降,那么如何擴容與保特性能呢?因為本質上這個事件數據是一個時間序列,所以第一步就是按時間維度分表。我們把每天的數據放在一張單獨的表中,表的命名方式是event_log_YYYY_MM_DD。這樣做有很多的好處

    1. 當前寫入表的記錄數量僅僅只有一天的數據量,提升插入的性能
    2. 由于大部分查詢都會有一個時間范圍,我們只需要查詢該時間范圍所涉及的表即可。
    3. 可以很方便地將歷史數據表歸檔。

    同時為了方便Ad-hoc的查詢,我們可以把多個單日表合并成一個月視圖或是年視圖。

    CREATE VIEW event_log_2014_01 AS  SELECT * from event_log_2014_01_01  UNION ALL  SELECT * from event_log_2014_01_02  
    ...UNION ALL  SELECT * from event_log_2014_01_31;  

    對于事件的寫入,由于時效性并不重要,所以我們應盡量將一段時間內的事件對象緩存在內存中,然后批量一次性的寫入。這樣對數據庫系統的負載會小很多。在實際的系統架構中,我們為用戶事件的收集與寫入單獨起一個Service進程,通過unix socket與web服務的主進程通信。

    事件數據庫的分布式擴容非常容易,可以通過user_id做為hash-key來分庫,也可以隨機分庫。然后只需簡單的通過增加數據庫集群中服務器的數量就可以擴容了。

    分析與統計

    由于我們對事件的屬性做了映射與hash,同時做了按天分表以及分布式的sharding,所以直接用SQL來對數據表查詢雖然可行,但并不是很方便。我們可以把數據分析常用的一些查詢寫成API的形式,并且把前面提到的那些復雜性都封裝在API的實現中。在系統中,我們稱這類API為Metrics API。在定義API接口的過程中,我們主要參考了Mixpanel的API接口定義

    整個Metrics API的方法數量小于10個,以下是3個比較常用的API

    def count(event_name, start_time, end_time, where=None):  
        ''' 返回所有符合條件的事件的總數
    
            >>> events('forum new topic', '2014/12/01', '2014/12/31', where={'room_id': 1})
            1321
    
            2014年12月所有在討論區1中發貼事件發生的總次數為1321。
        '''     ...def group(event_name, property, start_time, end_time, where=None):  
        ''' 對事件按某一個屬性進行分組,返回該屬性在這類事件中值的分布
    
            >>> group('share app', 'channel', '2014/12/01', '2014/12/31')
            {
                '非死book' : 786,
                '推ter'  : 439,
                'email'    : 300,
                'sms'      : 257,
            }
    
            2014年12月通過各個渠道分享app的次數統計
        '''     ...def retention(start_time, time_unit, retention_length, born_event, retention_event, where=None):  
        ''' 用戶的粘性分析,將某個時間段內誕生的用戶做為實驗組,觀察這組用戶在之后的幾個時間段里的活躍度
            誕生事件由born_event決定,活躍事件由retention_avent決定
    
            >>> retention('2014/12/01', 'week', 4, 'user created', 'app open', where={'platform': 'android'})
            {
                'cohort_size': 34032,
                'retentions': [0.54, 0.42, 0.31, 0.25]
            }
    
            以2014年12月1日為始的那一周(12/01 - 12/07)在Android平臺上注冊的用戶做為一個集合,共34032個用戶。
            他們中在之后第一周(12/08 - 12/14)打開app的人數占集合總數的54%
            他們中在之后第二周(12/15 - 12/21)打開app的人數占集合總數的42%
            他們中在之后第三周(12/22 - 12/28)打開app的人數占集合總數的31%
            他們中在之后第四周(12/29 - 01/04)打開app的人數占集合總數的25%
        '''     ...

    數據分析團隊是Metrics API的主要用戶,他們95%以上的工作都可以通過這套API來完成。開發團隊則會通過并發或是緩存等方法,持續的優化API的性能。

    總結

    這次與大家分享了基于關系數據庫的用戶事件存儲與分析,希望以后能將這套方案開源,但暫時還沒有具體的時間計劃。在本文的開始,我提過目前Glow正在向用Redshift + Hadoop + Hive的平臺轉型,等這部分工作完成后再和大家分享經驗。

    來自:http://tech.glowing.com/cn/user-events-storage-and-analytics/

     本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
  • sesese色