前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flink去重第四弹:bitmap精确去重

Flink去重第四弹:bitmap精确去重

作者头像
Flink实战剖析
发布2022-04-18 13:11:39
2.1K0
发布2022-04-18 13:11:39
举报
文章被收录于专栏:Flink实战剖析Flink实战剖析

Flink去重第一弹:MapState去重

Flink去重第二弹:SQL方式

Flink去重第三弹:HyperLogLog去重

关于hyperloglog去重优化

不得不掌握的三种BitMap

在前面提到的精确去重方案都是会保存全量的数据,但是这种方式是以牺牲存储为代价的,而hyperloglog方式虽然减少了存储但是损失了精度,那么如何能够做到精确去重又能不消耗太多的存储呢,这篇主要讲解如何使用bitmap做精确去重。

ID-mapping

在使用bitmap去重需要将去重的id转换为一串数字,但是我们去重的通常是一串包含字符的字符串例如设备ID,那么第一步需要将字符串转换为数字,首先可能想到对字符串做hash,但是hash是会存在概率冲突的,那么可以使用美团开源的leaf分布式唯一自增ID算法,也可以使用Twitter开源的snowflake分布式唯一ID雪花算法,我们选择了实现相对较为方便的snowflake算法(从网上找的),代码如下:

代码语言:javascript
复制
public class SnowFlake {

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1480166465631L;

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位数

    private final static long MACHINE_BIT = 5;   //机器标识占用的位数

    private final static long DATACENTER_BIT = 5;//数据中心占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;

    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId;  //数据中心

    private long machineId;     //机器标识

    private long sequence = 0L; //序列号

    private long lastStmp = -1L;//上一次时间戳

    public SnowFlake(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
                | datacenterId << DATACENTER_LEFT       //数据中心部分
                | machineId << MACHINE_LEFT             //机器标识部分
                | sequence;                             //序列号部分
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }


}

snowflake算法的实现是与机器码以及时间有关的,为了保证其高可用做了两个机器码不同的对外提供的服务。那么整个转换流程如下图:

首先会从Hbase中查询是否有UID对应的ID,如果有则直接获取,如果没有则会调用ID-Mapping服务,然后将其对应关系存储到Hbase中,最后返回ID至下游处理。

UDF化

为了方便提供业务方使用,同样需要将其封装成为UDF, 由于snowflake算法得到的是一个长整型,因此选择了Roaring64NavgabelMap作为存储对象,由于去重是按照维度来计算,所以使用UDAF,首先定义一个accumulator:

代码语言:javascript
复制
public class PreciseAccumulator{

    private Roaring64NavigableMap bitmap;

    public PreciseAccumulator(){
        bitmap=new Roaring64NavigableMap();
    }

    public void add(long id){
        bitmap.addLong(id);
    }

    public long getCardinality(){
        return bitmap.getLongCardinality();
    }
}

udaf实现

代码语言:javascript
复制
public class PreciseDistinct extends AggregateFunction<Long, PreciseAccumulator> {

    @Override public PreciseAccumulator createAccumulator() {
        return new PreciseAccumulator();
    }

    public void accumulate(PreciseAccumulator accumulator,long id){
        accumulator.add(id);
    }

    @Override public Long getValue(PreciseAccumulator accumulator) {
        return accumulator.getCardinality();
    }
}

那么在实际使用中只需要注册udaf即可。

关于去重系列就写到这里,如果您有不同的意见或者看法,欢迎私信。

—END—

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Flink实战剖析 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不得不掌握的三种BitMap
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档