我們開源的歷史:我們如何在 Go 中製作分析服務並使其公開可用

目前,世界上幾乎每家公司都會收集有關網路資源上的使用者操作的統計資料。 動機很明確——公司想知道他們的產品/網站是如何使用的,並且更了解他們的用戶。 當然,市場上有大量工具可以解決這個問題 - 來自以儀表板和圖表形式提供數據的分析系統(例如 Google Analytics)到客戶資料平台,它允許您從任何倉庫中的不同來源收集和聚合資料(例如 ).

但我們發現一個問題還沒解決。 就這樣誕生了 事件原生 — 開源分析服務。 了解我們為什麼決定開發自己的服務、它為我們帶來了什麼以及最終結果是什麼(透過程式碼片段)。

我們開源的歷史:我們如何在 Go 中製作分析服務並使其公開可用

我們為什麼要開發自己的服務?

那是九十年代,我們盡力生存。 2019年,我們開發了API First客戶資料平台 科感,這使得聚合來自不同來源(Facebook 廣告、Stripe、Salesforce、Google play、Google Analytics 等)的資料成為可能,以便更方便地進行資料分析、識別依賴關係等。 我們注意到許多用戶使用我們的平台進行數據分析,特別是 Google Analytics(以下簡稱 GA)。 我們與一些用戶交談,發現他們需要使用 GA 收到的產品分析數據,但是 谷歌樣本數據 對許多人來說,GA 使用者介面並不是方便的標準。 我們與用戶進行了足夠的對話,並意識到許多人也在使用 Segment 平台(順便說一下,就在前幾天) 以 3.2 億美元出售).

他們在其 Web 資源上安裝了 Segment javascript 像素,並將有關使用者行為的資料載入到指定的資料庫(例如 Postgres)中。 但 Segment 也有其缺點——價格。 例如,如果某個 Web 資源有 90,000 MTU(每月追蹤使用者),那麼您每月需要向收銀員支付約 1,000 美元。 還有第三個問題 - 某些瀏覽器擴充功能(例如 AdBlock)阻止了分析收集,因為... 來自瀏覽器的 http 請求被傳送到 GA 和 Segment 網域。 根據客戶的意願,我們創建了一項分析服務,可以收集完整的資料集(無需採樣),並且是免費的,並且可以在我們自己的基礎設施上運行。

服務如何運作

這項服務由三個部分組成:javascript Pixel(我們後來用 typescript 重寫了),伺服器部分用 GO 語言實現,計劃使用 Redshift 和 BigQuery 作為內部資料庫(後來他們添加了對Postgres、ClickHouse 和 Snowflake)。

決定保持 GA 和 Segment 活動的結構不變。 所需要做的就是將安裝像素的網路資源中的所有事件複製到我們的後端。 事實證明,這並不難做到。 Javascript 像素以新方法覆寫了原始 GA 庫方法,從而將事件複製到我們的系統中。

//'ga' - стандартное название переменной Google Analytics
if (window.ga) {
    ga(tracker => {
        var originalSendHitTask = tracker.get('sendHitTask');
        tracker.set('sendHitTask', (model) => {
            var payLoad = model.get('hitPayload');
            //отправка оригинального события в GA
            originalSendHitTask(model);
            let jsonPayload = this.parseQuery(payLoad);
            //отправка события в наш сервис
            this.send3p('ga', jsonPayload);
        });
    });
}

使用 Segment Pixel,一切都變得更簡單;它具有中間件方法,我們使用了其中之一。


//'analytics' - стандартное название переменной Segment
if (window.analytics) {
    if (window.analytics.addSourceMiddleware) {
        window.analytics.addSourceMiddleware(chain => {
            try {
		//дублирование события в наш сервис
                this.send3p('ajs', chain.payload);
            } catch (e) {
                LOG.warn('Failed to send an event', e)
            }
	    //отправка оригинального события в Segment
            chain.next(chain.payload);
        });
    } else {
        LOG.warn("Invalid interceptor state. Analytics js initialized, but not completely");
    }
} else {
    LOG.warn('Analytics.js listener is not set.');
}

除了複製事件之外,我們還添加了發送任意 json 的功能:


//Отправка событий с произвольным json объектом
eventN.track('product_page_view', {
    product_id: '1e48fb70-ef12-4ea9-ab10-fd0b910c49ce',
    product_price: 399.99,
    price_currency: 'USD'
    product_release_start: '2020-09-25T12:38:27.763000Z'
});

接下來我們來談談伺服器部分。 後端應該接受http請求,並用附加資訊填充它們,例如地理資料(感謝 最大思維 為此)並將其記錄在資料庫中。 我們希望使該服務盡可能方便,以便可以以最少的配置使用它。 我們已經實現了根據傳入 json 事件的結構來確定資料模式的功能。 資料類型由值定義。 嵌套物件被分解並簡化為平面結構:

//входящий json
{
  "field_1":  {
    "sub_field_1": "text1",
    "sub_field_2": 100
  },
  "field_2": "text2",
  "field_3": {
    "sub_field_1": {
      "sub_sub_field_1": "2020-09-25T12:38:27.763000Z"
    }
  }
}

//результат
{
  "field_1_sub_field_1":  "text1",
  "field_1_sub_field_2":  100,
  "field_2": "text2",
  "field_3_sub_field_1_sub_sub_field_1": "2020-09-25T12:38:27.763000Z"
}

然而,數組目前只是簡單地轉換為字串,因為並非所有關聯式資料庫都支援重複欄位。 也可以使用可選的對應規則來變更欄位名稱或刪除它們。 它們允許您在必要時更改資料模式或將一種資料類型轉換為另一種資料類型。 例如,如果 json 欄位包含帶有時間戳記的字串 (field_3_sub_field_1_sub_sub_field_1 從上面的例子),那麼為了在資料庫中建立時間戳類型的字段,需要在配置中編寫映射規則。 換句話說,欄位的資料類型首先由 json 值決定,然後套用類型轉換規則(如果配置)。 我們確定了 4 種主要資料類型:STRING、FLOAT64、INT64 和 TIMESTAMP。 映射和類型轉換規則如下所示:

rules:
  - "/field_1/subfield_1 -> " #правило удаления поля
  - "/field_2/subfield_1 -> /field_10/subfield_1" #правило переноса поля
  - "/field_3/subfield_1/subsubfield_1 -> (timestamp) /field_20" #правило переноса поля и приведения типа

確定資料類型的演算法:

  • 將 json 結構轉換為平面結構
  • 透過值確定欄位的資料類型
  • 應用程式映射和類型轉換規則

然後從傳入的json結構中:

{
    "product_id":  "1e48fb70-ef12-4ea9-ab10-fd0b910c49ce",
    "product_price": 399.99,
    "price_currency": "USD",
    "product_type": "supplies",
    "product_release_start": "2020-09-25T12:38:27.763000Z",
    "images": {
      "main": "picture1",
      "sub":  "picture2"
    }
}

將獲得數據模式:

"product_id" character varying,
"product_price" numeric (38,18),
"price_currency" character varying,
"product_type" character varying,
"product_release_start" timestamp,
"images_main" character varying,
"images_sub" character varying

我們也認為使用者應該能夠根據其他標準配置分區或劃分資料庫中的數據,並實現使用常數或設定表名的功能 表達 在配置中。 在下面的範例中,事件將被保存到一個表中,該表的名稱是根據product_type和_timestamp欄位的值計算的(例如 耗材_2020_10):

tableName: '{{.product_type}}_{{._timestamp.Format "2006_01"}}'

但是,傳入事件的結構可能會在運作時發生變化。 我們實作了一種演算法來檢查現有表的結構與傳入事件的結構之間的差異。 如果發現差異,表將使用新欄位進行更新。 為此,請使用修補 SQL 查詢:

#Пример для Postgres
ALTER TABLE "schema"."table" ADD COLUMN new_column character varying

架構

我們開源的歷史:我們如何在 Go 中製作分析服務並使其公開可用

為什麼需要將事件寫入檔案系統,而不是直接寫入資料庫? 在處理大量插入時,資料庫並不總是表現良好(Postgres 建議)。 為此,Logger 將傳入事件寫入文件,並在單獨的 goroutine(線程)中文件讀取器讀取文件,然後轉換並確定資料。 表管理器確保表架構是最新的後,會將資料批量寫入資料庫。 隨後,我們添加了將資料直接寫入資料庫的功能,但我們將這種模式用於數量不多的事件 - 例如轉換。

開源和未來計劃

在某種程度上,該服務開始看起來像一個成熟的產品,我們決定將其發佈為開源。 目前,已實現與 Postgres、ClickHouse、BigQuery、Redshift、S3、Snowflake 的整合。 所有整合都支援資料載入的批次和流模式。 新增了對透過 API 請求的支援。

目前的整合方案如下所示:

我們開源的歷史:我們如何在 Go 中製作分析服務並使其公開可用

雖然服務可以獨立使用(例如使用Docker),但我們也有 託管版本,您可以在其中設定與資料倉儲的整合、將 CNAME 新增至您的網域並查看事件數量的統計資料。 我們的近期計劃是不僅能夠聚合來自網路資源的統計數據,還能夠聚合來自外部資料來源的數據,並將它們保存到您選擇的任何儲存中!

→ GitHub上
→ Документация
→ 鬆弛

如果 EventNative 能幫助您解決問題,我們將非常高興!

只有註冊用戶才能參與調查。 登入, 請。

貴公司使用什麼統計收集系統?

  • 企業排放佔全球 48,0%Google分析12

  • 企業排放佔全球 4,0%段1

  • 企業排放佔全球 16,0%另一種(寫在評論裡)4

  • 企業排放佔全球 32,0%實施您的服務8

25 位用戶投票。 6 名用戶棄權。

來源: www.habr.com

添加評論