真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

優(yōu)雅快速的統(tǒng)計千萬級別uv

定義

PV是page view的縮寫,即頁面瀏覽量,通常是衡量一個網(wǎng)絡(luò)新聞頻道或網(wǎng)站甚至一條網(wǎng)絡(luò)新聞的主要指標(biāo)。網(wǎng)頁瀏覽數(shù)是評價網(wǎng)站流量最常用的指標(biāo)之一,簡稱為PV

創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都做網(wǎng)站、網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)、開州網(wǎng)絡(luò)推廣、重慶小程序開發(fā)、開州網(wǎng)絡(luò)營銷、開州企業(yè)策劃、開州品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供開州建站搭建服務(wù),24小時服務(wù)熱線:18980820575,官方網(wǎng)址:www.cdcxhl.com

UV是unique visitor的簡寫,是指通過互聯(lián)網(wǎng)訪問、瀏覽這個網(wǎng)頁的自然人。

通過以上的概念,可以清晰的看出pv是比較好設(shè)計的,網(wǎng)站的每一次被訪問,pv都會增加,但是uv就不一定會增加了,uv本質(zhì)上記錄的是按照某個標(biāo)準(zhǔn)劃分的自然人,這個標(biāo)準(zhǔn)其實(shí)我們可以自己去定義,比如:可以定義同一個IP的訪問者為同一個UV,這也是最常見的uv定義之一,另外還有根據(jù)cookie定義等等。無論是pv還是uv,都需要一個時間段來加以描述,平時我們所說的pv,uv數(shù)量指的都是24小時之內(nèi)(一個自然日)的數(shù)據(jù)。

pv相比較uv來說,技術(shù)上比較容易一些,今天咱們就來說一說uv的統(tǒng)計,為什么說uv的統(tǒng)計相對來說比較難呢,因為uv涉及到同一個標(biāo)準(zhǔn)下的自然人的去重,尤其是一個uv千萬級別的網(wǎng)站,設(shè)計一個好的uv統(tǒng)計系統(tǒng)也許并非想象的那么容易。

那我們就來設(shè)計一個以一個自然日為時間段的uv統(tǒng)計系統(tǒng),一個自然人(uv)的定義為同一個來源IP(當(dāng)然你也可以自定義其他標(biāo)準(zhǔn)),數(shù)據(jù)量級別假設(shè)為每日千萬uv的量級。

注意:今天我們討論的重點(diǎn)是獲取到自然人定義的信息之后如何設(shè)計uv統(tǒng)計系統(tǒng),并非是如何獲取自然人的定義。uv系統(tǒng)的設(shè)計并非想象的那么簡單,因為uv可能隨著網(wǎng)站的營銷策略會出現(xiàn)瞬間大流量,比如網(wǎng)站舉辦了一個秒殺活動。

基于DB方案

服務(wù)端編程有一句名言曰:沒有一個表解決不了的功能,如果有那就兩個表三個表。一個uv統(tǒng)計系統(tǒng)確實(shí)可以基于數(shù)據(jù)庫來實(shí)現(xiàn),而且也不復(fù)雜,uv統(tǒng)計的記錄表可以類似如下(不要太糾結(jié)以下表設(shè)計是否合理):

字段類型描述
IP varchar(30) 客戶端來源ip
DayID int 時間的簡寫,例如 20190629
其他字段 int 其他字段描述

當(dāng)一個請求到達(dá)服務(wù)器,服務(wù)端每次需要查詢一次數(shù)據(jù)庫是否有當(dāng)前IP和當(dāng)前時間的訪問記錄,如果有,則說明是同一個uv,如果沒有,則說明是新的uv記錄,插入數(shù)據(jù)庫。當(dāng)然以上兩步也可以寫到一個sql語句中:

if exists( select 1 from table where ip='ip' and dayid=dayid )
  Begin
    return 0
  End
else
  Begin
     insert into table .......
  End

所有基于數(shù)據(jù)庫的解決方案,在數(shù)據(jù)量大的情況下幾乎都更容易出現(xiàn)瓶頸。面對每天千萬級別的uv統(tǒng)計,基于數(shù)據(jù)庫的這種方案也許并不是最優(yōu)的。

優(yōu)化方案

面對每一個系統(tǒng)的設(shè)計,我們都應(yīng)該沉下心來思考具體的業(yè)務(wù)。至于uv統(tǒng)計這個業(yè)務(wù)有幾個特點(diǎn):

  1. 每次請求都需要判斷是否已經(jīng)存在相同的uv記錄

  2. 持久化uv數(shù)據(jù)不能影響正常的業(yè)務(wù)

  3. uv數(shù)據(jù)的準(zhǔn)確性可以忍受一定程度的誤差
哈希表

基于數(shù)據(jù)庫的方案中,在大數(shù)據(jù)量的情況下,性能的瓶頸引發(fā)原因之一就是:判斷是否已經(jīng)存在相同記錄,所以要優(yōu)化這個系統(tǒng),肯定首先是要優(yōu)化這個步驟。根據(jù)菜菜以前的文章,是否可以想到解決這個問題的數(shù)據(jù)結(jié)構(gòu),對,就是哈希表。哈希表根據(jù)key來查找value的時間復(fù)雜度為O(1)常數(shù)級別,可以完美的解決查找相同記錄的操作瓶頸。

也許在uv數(shù)據(jù)量比較小的時候,哈希表也許是個不錯的選擇,但是面對千萬級別的uv數(shù)據(jù)量,哈希表的哈希沖突和擴(kuò)容,以及哈希表占用的內(nèi)存也許并不是好的選擇了。假設(shè)哈希表的每個key和value 占用10字節(jié),1千萬的uv數(shù)據(jù)大約占用 100M,對于現(xiàn)代計算機(jī)來說,100M其實(shí)不算大,但是有沒有更好的方案呢?

優(yōu)化哈希表

基于哈希表的方案,在千萬級別數(shù)據(jù)量的情況下,只能算是勉強(qiáng)應(yīng)付,如果是10億的數(shù)據(jù)量呢?那有沒有更好的辦法搞定10億級數(shù)據(jù)量的uv統(tǒng)計呢?這里拋開持久化數(shù)據(jù),因為持久化設(shè)計到數(shù)據(jù)庫的分表分庫等優(yōu)化策略了,咱們以后再談。有沒有更好的辦法去快速判斷在10億級別的uv中某條記錄是否存在呢?

為了盡量縮小使用的內(nèi)存,我們可以這樣設(shè)計,可以預(yù)先分配bit類型的數(shù)組,數(shù)組的大小是統(tǒng)計的最大數(shù)據(jù)量的一個倍數(shù),這個倍數(shù)可以自定義調(diào)整。現(xiàn)在假設(shè)系統(tǒng)的uv最大數(shù)據(jù)量為1千萬,系統(tǒng)可以預(yù)先分配一個長度為5千萬的bit數(shù)組,bit占用的內(nèi)存最小,只占用一位。按照一個哈希沖突比較小的哈希函數(shù)計算每一個數(shù)據(jù)的哈希值,并設(shè)置bit數(shù)組相應(yīng)哈希值位置的值為1。由于哈希函數(shù)都有沖突,有可能不同的數(shù)據(jù)會出現(xiàn)相同的哈希值,出現(xiàn)誤判,但是我們可以用多個不同的哈希函數(shù)來計算同一個數(shù)據(jù),來產(chǎn)生不同的哈希值,同時把這多個哈希值的數(shù)組位置都設(shè)置為1,從而大大減少了誤判率,剛才新建的數(shù)組為最大數(shù)據(jù)量的一個倍數(shù)也是為了減小沖突的一種方式(容量越大,沖突越小)。當(dāng)一個1千萬的uv數(shù)據(jù)量級,5千萬的bit數(shù)組占用內(nèi)存才幾十M而已,比哈希表要小很多,在10億級別下內(nèi)存占用差距將會更大。

以下為代碼示例:

class BloomFilter
    {
        BitArray container = null;
      public BloomFilter(int length)
        {
            container = new BitArray(length);
        }

        public void Set(string key)
        {
            var h2 = Hash2(key);
            var h3 = Hash3(key);
            var h4 = Hash4(key);
            var h5 = Hash5(key);
            container[h2] = true;
            container[h3] = true;
            container[h4] = true;
            container[h5] = true;

        }
        public bool Get(string key)
        {
            var h2 = Hash2(key);
            var h3 = Hash3(key);
            var h4 = Hash4(key);
            var h5 = Hash5(key);

            return container[h2] && container[h3] && container[h4] && container[h5];
        }

        //模擬哈希函數(shù)1
         int Hash2(string key)
        {
            int hash = 5381;
            int i;
            int count;
            char[] bitarray = key.ToCharArray();
            count = bitarray.Length;
            while (count > 0)
            {
                hash += (hash << 5) + (bitarray[bitarray.Length - count]);
                count--;
            }
            return (hash & 0x7FFFFFFF) % container.Length;

        }
         int Hash3(string key)
        {
            int seed = 131; // 31 131 1313 13131 131313 etc..
            int hash = 0;
            int count;
            char[] bitarray = (key+"key2").ToCharArray();
            count = bitarray.Length;
            while (count > 0)
            {
                hash = hash * seed + (bitarray[bitarray.Length - count]);
                count--;
            }

            return (hash & 0x7FFFFFFF)% container.Length;
        }
         int Hash4(string key)
        {
            int hash = 0;
            int i;
            int count;
            char[] bitarray = (key + "keykey3").ToCharArray();
            count = bitarray.Length;
            for (i = 0; i < count; i++)
            {
                if ((i & 1) == 0)
                {
                    hash ^= ((hash << 7) ^ (bitarray[i]) ^ (hash >> 3));
                }
                else
                {
                    hash ^= (~((hash << 11) ^ (bitarray[i]) ^ (hash >> 5)));
                }
                count--;
            }

            return (hash & 0x7FFFFFFF) % container.Length;

        }
        int Hash5(string key)
        {
            int hash = 5381;
            int i;
            int count;
            char[] bitarray = (key + "keykeyke4").ToCharArray();
            count = bitarray.Length;
            while (count > 0)
            {
                hash += (hash << 5) + (bitarray[bitarray.Length - count]);
                count--;
            }
            return (hash & 0x7FFFFFFF) % container.Length;
        }
    }

測試程序為:

BloomFilter bf = new BloomFilter(200000000);
            int exsitNumber = 0;
            int noExsitNumber = 0;

            for (int i=0;i < 10000000; i++)
            {
                string key = $"ip_{i}";
                var isExsit= bf.Get(key);
                if (isExsit)
                {
                    exsitNumber += 1;
                }
                else
                {
                    bf.Set(key);
                    noExsitNumber += 1;
                }
            }
            Console.WriteLine($"判斷存在的數(shù)據(jù)量:{exsitNumber}");
            Console.WriteLine($"判斷不存在的數(shù)據(jù)量:{noExsitNumber}");

測試結(jié)果:

判斷存在的數(shù)據(jù)量:7017
判斷不存在的數(shù)據(jù)量:×××983

優(yōu)雅快速的統(tǒng)計千萬級別uv

占用內(nèi)存40M,誤判率不到 千分之1,在這個業(yè)務(wù)場景下在可接受范圍之內(nèi)。在真正的業(yè)務(wù)當(dāng)中,系統(tǒng)并不會在啟動之初就分配這么大的bit數(shù)組,而是隨著沖突增多慢慢擴(kuò)容到一定容量的。

異步優(yōu)化

當(dāng)判斷一個數(shù)據(jù)是否已經(jīng)存在這個過程解決之后,下一個步驟就是把數(shù)據(jù)持久化到DB,如果數(shù)據(jù)量較大或者瞬間數(shù)據(jù)量較大,可以考慮使用mq或者讀寫IO比較大的NOSQL來代替直接插入關(guān)系型數(shù)據(jù)庫。

優(yōu)雅快速的統(tǒng)計千萬級別uv

思路一轉(zhuǎn),整個的uv流程其實(shí)也都可以異步化,而且也推薦這么做。

優(yōu)雅快速的統(tǒng)計千萬級別uv


網(wǎng)頁名稱:優(yōu)雅快速的統(tǒng)計千萬級別uv
當(dāng)前鏈接:http://weahome.cn/article/gcjied.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部