海量数据处理

简介: 海量数据处理

一、位图

1.1 位图概念

位图就是用每一位来存放某种状态,适用于海量数据且数据无重复的场景。通常是用来判断某个数据是否存在的。


1.2 位图的实现

位图的底层结构依然是连续的线性空间,这里我们使用vector容器作为其底层结构。


#include <iostream>
#include <vector>
using std::vector;
using std::cout;
using std::endl;
namespace bjy {
  template<size_t N>
  class bitset {
  public:
    bitset() {
      if (N % 8 == 0) _bits.resize(N / 8, 0);
      else _bits.resize(N / 8 + 1, 0);
    }
    void set(size_t which){//将某个位设置为1
      size_t i = which / 8, j = which % 8;
      _bits[i] |= (1 << j);
    }
    void reset(size_t which) {//将某个位设置为0
      size_t i = which / 8, j = which % 8;
      _bits[i] &= ~(1 << j);
    }
    bool test(size_t which)const {
      size_t i = which / 8, j = which % 8;
      return _bits[i] & (1 << j);
    }
  private:
    vector<char> _bits;
  };

1.3 位图的特点

1. 速度快且节省时间。


2. 采用直接定址法,不存在哈希冲突。


3. 相对局限,只能处理整型数据。


4. STL中实现的bitset容器存在一定不足,其底层使用静态数组实现。其开辟在栈区会导致在处理海量数据时极易发生StackOverflow。(使用时应特别注意)


二、布隆过滤器

2.1 布隆过滤器的提出

当使用新闻客户端看新闻时,其会不停地推荐新的内容,每次推荐时要去重。但新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何实现的呢?


方案一: 使用哈希表存储用户记录,但空间消耗过大。


方案二: 使用位图记录用户信息,明显不可取(位图只能处理整型数据)


方案三: 将位图与哈希进行结合,即布隆过滤器


2.2 概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询。可以用来告诉你 "某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

440afa9d903d45fa9f2c5c409c5ccab6.png



Bloom Filters (jasondavies.com)

https://www.jasondavies.com/bloomfilter/


2.3 实现原理

布隆过滤器是一个 bit 向量或者说 bit 数组

e9e4deadd0aa452b98faa1727578f975.png



若要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成多个哈希值,并对每个生成的哈希值指向的 bit 位置进行set操作。例如针对key "百度" 用三个不同的哈希函数分别生成了哈希值 0、3、6,则上图转变为:

0e5e0da751754111af9a99843782ad37.png



 再存一个值 "腾讯",若哈希函数返回 2、3、7 的话,图继续变为:


a1faffd097c24e90886c154a401240d2.png


3 这个bit位由于两个key的哈希函数都返回了3,因此它被覆盖了。


现在若想查询 "b站" 这个key是否存在,哈希函数返回了 0、4、7三个值,结果我们发现 4 这个 bit 位上的值为 0,说明没有任何一个key映射到这个 bit 位上,因此我们可以很确定地说 "b站"这个key不存在。而当我们需要查询 "百度" 这个值是否存在的话,那么哈希函数必然会返回 0、3、6,然后我们检查发现这三个 bit 位上的值均为 1,那么说明"baidu"可能存在。


为什么不是一定存在呢?因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个key即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他key置为了 1 ,那么程序还是会判断这个key存在。


2.4 哈希函数个数和布隆过滤器长度的选择

在使用布隆过滤器时,哈希函数的个数和布隆过滤长度的设定都是我们需要考虑的问题。


1. 哈希函数个数越多则布隆过滤器bit位被置为1的速度越快,且会效率下降。但是若哈希函数过少则会导致误报率较高。


2. 布隆过滤器的长度会直接影响误报率,布隆过滤器长度越长则误报率越低。但若是过长了则失去布隆过滤器原有的优势(占用空间小),反而还不如选择哈希表。


直接根据下图中的公式选择即可


85df2eda421b4b6fbb3074e9990999ad.png


2.5 布隆过滤器的删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计

数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储

空间的代价来增加删除操作。


缺陷:

1. 无法确认元素是否真正在布隆过滤器中

2. 存在计数回绕


2.6 特点

2.6.1 优点

1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关

2. 哈希函数相互之间没有关系,方便硬件并行运算

3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

4. 在能够承受一定误判的情况下,布隆过滤器比其他数据结构有这很大的空间优势

5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能

6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算


2.6.2 缺点

1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再

建立一个白名单,存储可能会误判的数据)

2. 不能获取元素本身

3. 一般情况下不能从布隆过滤器中删除元素

4. 如果采用计数方式删除,可能会存在计数回绕问题


2.7 完整代码实现

#include <iostream>
#include <vector>
#include <string>
#include <bitset>
using namespace std;
namespace bjy {
  struct HashBKDR {
    size_t operator()(const string& key) {
      size_t sum = 0;
      for (auto& e : key) {
        sum = sum * 131 + e;
      }
      return sum;
    }
  };
  struct HashAP {
    size_t operator()(const string& key) {
      size_t hash = 0;
      for (size_t i = 0; i < key.size(); i++) {
        if ((i & 1) == 0) {
          hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
        }
        else {
          hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
        }
      }
      return hash;
    }
  };
  struct HashDJB {
    size_t operator()(const string& key) {
      size_t hash = 5381;
      for (auto ch : key) {
        hash += (hash << 5) + ch;
      }
      return hash;
    }
  };
  template<size_t N, class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
  class Bloon_filter
  {
  public:
    void set(const K& key) {
      size_t pos1 = Hash1()(key) % (_ratio * N);
      size_t pos2 = Hash2()(key) % (_ratio * N);
      size_t pos3 = Hash3()(key) % (_ratio * N);
      _bits->set(pos1);
      _bits->set(pos2);
      _bits->set(pos3);
    }
    bool test(const K& key) {
      size_t pos1 = Hash1()(key) % (_ratio * N);
      size_t pos2 = Hash2()(key) % (_ratio * N);
      size_t pos3 = Hash3()(key) % (_ratio * N);
      if (_bits->test(pos1) && _bits->test(pos2) && _bits->test(pos3)) return true;
      else return false;
    }
  private:
    const static size_t _ratio = 5;
    bitset<_ratio* N>* _bits = new bitset<_ratio* N>;//库中bitset使用静态数组,开辟在栈上,容易导致栈溢出
  };
}

三、海量数据处理常见面试题

1. 给定100亿个整数,设计算法找到只出现一次的整数

利用两个位图完成该问题的实现。采用KV的统计次数的模型,两个位图中各有一个bit位表示同一个整型。00表示该整型出现次数为0,01表示该整型出现次数为1,10表示该整型出现次数为2次,11表示该整型出现3次及以上。

template<size_t N>
class twobitset
{
public:
  void set(size_t x)
  {
    bool inset1 = _bs1.test(x);
    bool inset2 = _bs2.test(x);
    if (inset1 == false && inset2 == false)//00
        {
      _bs2.set(x);// -> 01
    }
    else if (inset1 == false && inset2 == true)//01
    {
      _bs1.set(x);
      _bs2.reset(x);// ->10
    }
    else if (inset1 == true && inset2 == false)//10
    {
      // ->11
      _bs1.set(x);
      _bs2.set(x);
    }
  }
  void print_once_num(){
    for (size_t i = 0; i < N; ++i){
      if (_bs1.test(i) == false && _bs2.test(i) == true){
        cout << i << endl;
      }
    }
  }
private:
  bitset<N> _bs1;
  bitset<N> _bs2;
};

2. 两个文件分别有100亿个整数,只有1G内存,找到两个文件交集

将文件A中的数据映射到位图A中,将文件B中的数据映射到位图B中,位图A和位图B对应且都为1的位所代表的key的集合即是两个文件的交集。


3. 给两个文件,分别有100亿个query,只有1G内存,找到两个文件交集。分别给出精确算法和近似算法

该问题不可使用bitset解决,其只适用于整型数据。query为字符串,应使用哈希切割思想或者布隆过滤器完成问题。

近似算法:

使用两个布隆过滤器分别处理两个文件,若两个布隆过滤器set(key)都为1,即这个key在两个文件中都可能存在,近似看做交集中的元素。

精确算法:

假设1个query为30Byte,100亿个query则大概需要300G以上的内存空间。我们的电脑肯定没有这么大的空间,所以这里需要使用哈希切割的思想。


f554e00d2be34962b778264509306321.png


若是处理完后某个小文件过大,可以换一个哈希函数对小文件再次进行哈希切割。


4. 给一个超过100G大小的log file, log中存着IP地址。(1)设计算法找到出现次数最多的IP地址。(2)找到top-K的IP。

问题一:

使用哈希切割,将一个大的log file分成500个小log file,相同的IP一定进入同一个小log file。再用map<string,int> 或者unordered_map<string,int>对每个小log file进行次数统计,找出出现次数最多的IP地址。

问题二:

建立一个K个<IP,count>元素的小堆(优先级队列)


目录
相关文章
|
4月前
|
存储 算法 物联网
海量数据实时计算利器:深入探索Tec(一个假设性技术框架)
总之,Tec作为海量数据实时计算利器,在推动数字化转型、提升业务效率、保障数据安全等方面发挥着重要作用。随着技术的不断进步和应用场景的不断拓展,Tec的未来发展前景将更加广阔。
|
存储 NoSQL 关系型数据库
高并发下的海量数据处理
前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger 种一棵树最好的时间是十年前,其次是现在
382 0
|
存储 机器学习/深度学习 分布式计算
大数据架构
大厂都在用什么大数据架构
3128 0
大数据架构
|
存储 分布式计算 大数据
探寻流式计算
流计算的出现拓宽了应对复杂实时计算需求能力。Storm作为流计算的利器,极大方便了应用。
Uma
|
存储 SQL OLAP
DTCC 2019 | 海量数据毫秒级分析的背后——《阿里超大规模实时数仓架构挑战与实践解析》
在DTCC 2019大会上,阿里云智能数据库产品事业部研究员林亮进行了题为《超大规模实时数仓架构挑战与实践解析》的分享,数据分析领域目前正在朝着在线化方向演进,数据业务在海量数据实时写入、高并发分析、稳定性、灵活性上挑战巨大。
Uma
4766 0
|
存储 大数据 数据库
什么是大数据架构?
大数据架构是用于摄取和处理大量数据(通常称为“大数据”)的总体系统,因此可以针对业务目的进行分析。该架构可视为基于组织业务需求的大数据解决方案的蓝图。 大数据架构是用于摄取和处理大量数据(通常称为“大数据”)的总体系统,因此可以针对业务目的进行分析。
2519 0
|
存储 NoSQL 分布式数据库
海量数据的分库分表技术演进,最佳实践
每个优秀的程序员和架构师都应该掌握分库分表,移动互联网时代,海量的用户每天产生海量的数量 用户表 订单表 交易流水表 以支付宝用户为例,8亿;微信用户更是10亿。
2303 0
|
关系型数据库 数据处理 数据库
下一篇
无影云桌面