微信号:infoqchina

介绍:有内容的技术社区媒体

Fourinone-4.17.10 (四不像)新版本发布 ​:单机毫秒完成上亿大数据常规统计

2017-12-25 08:00 InfoQ

Fourinone-4.17.10 新版本内容:

虽然现在最火的是 AI,但是大数据和计算能力仍然是机器学习 /AI 算法的重要支撑,我们的业务场景大部分是通过手机终端、服务器日志不断产生日志数据,通过消息通道发送到大数据平台进行存储、加工和统计,然后在统计数据之上提供算法挖掘用户偏好行为和画像,为此,我们的关键任务是需要从海量数据里统计分析每项产品的去重用户、新增用户、pv、uv、dau(日活)、mau(月活)等指标,这个过程存储占用越少,计算时间越快越好。Fourinone(CoolHash) 拥有原创数据库引擎设计能力和知识产权,能够在引擎层面灵活扩充各种功能支持,为了提供大数据统计计算的最优解决方案,4.17 在引擎上增强了以下特性:

1 增加了自加和存在新增两个原子操作
1、Object putPlus(String key, T plusValue)

如果 key 对应的 value 是数字类型(int、long、double、float),自增加 plusValue(数字类型),如 plusValue=1,表示每次自增 1,plusValue 也可以是小数。如果 key 对应的 value 是字符串类型,自增加 plusValue(字符串),会累加到原字符串后面,可以用分隔符隔开。putPlus 的返回值为该 key 的上一个值。

2、Object putNx(String key, T value)

如果 key 存在,则不操作,如不存在写入 value。putNx 返回值为 key 操作前值,为 null 表示不存在,否则返回已有值。

利用 putPlus 和 putNx 可以完成很多原子操作,如 count 类计数统计,在开源包指南附带的 CountDemo.java 里的 countTest 方法演示了 putPlus 的使用,在 ThreadClient.java 的 putPlusTest 方法和 putNxTest 方法演示了多线程下的使用。

pvTest 方法演示了计算 pv,如果 id 不存在则写入,并将 pv 数自加 1,其他线程发现 id 存在,则无法更新 pv 数

Object nx = chc.putNx("v0_"+i, i);
if(nx==null)
    chc.putPlus("pv_v0",1);
2 增加了客户端本地和存储引擎端强大的 bitmap 支持

上面通过 putPlus 和 putNx 原子操作可以计算 pv,但并不是最高效的方案,使用 bitmap 有两个非常显著的优势:位存储占用空间低,位计算效率高。将需要做统计计算的 id 转换成数字序号,每个只占 1 个 bit,对于 20 亿的用户 id,只需要 20 亿 bit 约 238m 大小,压缩后占用空间更小,最少只要 200k;通过单个 bitmap 可以完成去重操作,通过多个 bitmap 的且、或、异或、反等位操作可以完成日活、月活、小时分钟活跃、重度用户、新增用户、用户流向等绝大部分的统计计算,而且能在单机毫秒级完成,真正做到实时计算出结果,同比 hadoop/hive 离线计算执行“select distinct count…from…groupby join…”类似 sql 的方式统计,往往需要几百台机器,耗用 30 分钟才能完成,对比非常悬殊,而且容易形成大量 sql 任务调度和大表 join 给集群带来繁重压力。

  • 1、去重用户:求 1 的总数

  • 2、活跃用户: 取或

    bitmap1 | bitmap2

  • 3、非活跃用户:取反:

    ~bitmap1

  • 4、重度用户:取且:

    Bitmap1 & bitmap2

  • 5、新增用户:取或加异或:

    (Bitmap1 | bitmap2)^bitmap1

  • 6、多种指标组合:

  • Bitmap1 & bitmap2 & bitmap3 &…

  • 等等

同时提供 bitmap 本地和引擎端互通实现,能够进行更灵活的架构设计,可以将 bitmap 压缩存储到任何数据库上,客户端拉回后完成聚合计算,计算完成的结果再写回数据库。也可以多个客户端同时连接到 CoolHash 存储引擎上,通过引擎的 bitmap 操作支持完成去重、聚合、解压缩等支持。BitMap 结合存储引擎如下图:

1、本地内存实现,CoolBitSet 实现了以下 bitmap 功能:

CoolBitSet(int maxSize),可指定大小限制,默认 1000 万大小,本地没有最大限制,可以使用多个分区的 bitmap 表示整型范围或长整型范围的数据,每个 1000 万的 bitmap 压缩后在 2m 以内,很适合放入 kv 存储。

  1. 基本操作:CoolBitSet 提供基本的 get(int n)、set(int n)、put(int n) 操作,其中 put 为存在返回 get,不存在 set,除外还提供批量操作:int set(CoolBitSet cbs): 将另外一个 bitmap 对象合并到当前 bitmap,并返回新增的数量。

  2. 聚合操作:求且、求或、异或、求反、求新增

    • CoolBitSet and(CoolBitSet cbs):两个 CoolBitSet 求且,更新到当前对象,并返回该对象引用

    • CoolBitSet or(CoolBitSet cbs):两个 CoolBitSet 求或,同上

    • CoolBitSet xor(CoolBitSet cbs):两个 CoolBitSet 求异或,同上

    • CoolBitSet andnot():将该 CoolBitSet 对象求反,同上

    • CoolBitSet setNew(CoolBitSet cbs):求当前 CoolBitSet 的新增用户,并返回新增用户结果的对象引用

  3. 求总数:int getTotal() 返回该 CoolBitSet 的用户总数,bit 位是 1 的总数量

  4. 求容量:int getSize() 返回该 CoolBitSet 的容量大小

  5. 调试查看:String toString(int num) 返回该 CoolBitSet 的二进制字符串,为了减少长度,参数 num 为需要查看的 byte 数,如 num=5 表示查看前 5 个 byte 的二进制串

和 java 的 bitmap 的实现区别:jdk 自带的 BitSet 类是以 long 数组实现,而且只能初始化大小,无法限制大小,每个 bitset 要耗用几百 m 的内存,多个 bitmap 容易造成空间大量浪费,BitSet 类只是本地内存实现,没有分布式存储引擎持久化支持。

2、引擎端持久化实现,CoolHashClient 提供了以下接口用来操作存储引擎:

  1. int putBitSet(String key, int index):

    单项操作,类似 CoolBitSet 的 put,第一个参数为 bitmap 的 key,第二个参数将该 bitmap 的 index 位置设为 1。

  2. boolean getBitSet(String key, int index):

    单项操作,类似 CoolBitSet 的 get,第一个参数为 bitmap 的 key,第二个参数需要获取的 index 位置的值。

  3. int putBitSet(String key, CoolBitSet cbs):

    批量操作,类似 CoolBitSet 的批量 set,将另外一个 bitmap 对象合并到指定 key 的 bitmap,并返回新增的数量。获取 CoolBitSet 对象仍然使用 get 接口 Object get(String key)

  4. Object putBitSet(String key, CoolBitSet cbs, String logical):

    聚合操作,参数 logical 可以设置为“and”,“or”,