首页 > 独立用户数去重统计方法

独立用户数去重统计方法

网站现在有大量用户,用户的id是纯数字。
每天用户都会购买物品,购买记录实时记录到订单表中。我们可以从中导出付费用户号码包。
现在需要统计每天、每月、每半年、每年的付费独立用户数(要去重),请问怎么设计数据库表来存储呢?

因为每天这个数据都会变化,所以是打算每天凌晨跑一个脚本,统计前一天的数据。但是每天都要去重会费好长时间,有什么成熟的或者合适的方法解决这个问题吗?


建议使用基数估计来完成去重统计功能;基数估计算法就是使用准确性换取空间。
解决这一问题的常见办法是使用位图[Bit-Map]
位图可以快速、准确地获取一个给定输入的基数。
位图的基本思想是使用哈希函数把数据集映射到一个bit位,每个输入元素与bit位是一一对应。
这样Hash将没有产生碰撞冲突,并减少需要计算每个元素映射到1个bit的空间。
虽然Bit-map大大节省了存储空间,但当统计很高的基数或非常大的不同的数据集,它们仍然有问题。
例如,如果我们想要使用Bit-map计数十亿,你将需要Bit-map位,或需要每个约120 MB的计数器。稀疏的位图可以被压缩,以获得更多的空间效率。
主要有三种算法实现:
hyperloglog,LogLog,LinearCounting
目前redis已经有hyperloglog实现;
下面是使用stream-lib结合levelDB的一个封装:

/**
 * 基于StreamLib以及LevelDB实现的基数估算
 * 
 * @author zxc Aug 2, 2013 3:45:27 PM
 */
public class StreamLibCardinality implements Cardinality, PandoraConstants {

    private static PandoraDataStore pandora = PandoraDataStore.getInstance();
    private static FireMap          fireMap = pandora.getMap(CARDINALITY_MAP);

    private static StreamLibCardinality      cardinality;

    private StreamLibCardinality() {

    }

    public static synchronized StreamLibCardinality getInstance() {
        if (cardinality == null) {
            cardinality = new StreamLibCardinality();
        }
        return cardinality;
    }

    public void add(String key, String... values) {
        if (values == null || values.length == 0) {
            return;
        }
        FireQueue fireQueue = pandora.getQueue(CARDINALITY_QUEUE + key);
        for (String value : values) {
            fireQueue.push(value.getBytes());
        }
    }

    public long count(String key) {
        FireQueue fireQueue = pandora.getQueue(CARDINALITY_QUEUE + key);
        ICardinality icardinality = get(key);
        boolean flag = false;
        while (true) {
            byte[] bytes = null;
            try {
                bytes = fireQueue.pop();
            } catch (Exception e) {
                break;
            }
            if (bytes != null) {
                String value = new String(bytes);
                icardinality.offer(value);
                flag = true;
            }
            if (bytes == null) {
                byte[] _bytes = fireQueue.pop();
                if (_bytes == null) {
                    break;
                }
            }
        }
        if (flag) {
            set(key, icardinality);
        }
        return icardinality.cardinality();
    }

    public byte[] bitmap(String key) throws Exception {
        ICardinality icardinality = new LinearCounting(fireMap.get(key));
        return icardinality.getBytes();
    }

    public void store(String key) {
        ICardinality icardinality = get(key);
        set(key, icardinality);
    }

    private ICardinality get(String key) {
        ICardinality icardinality;
        byte[] fireMapByte = fireMap.get(key);
        if (fireMapByte == null || fireMapByte.length <= 0) {
            icardinality = LinearCounting.Builder.onePercentError(Integer.MAX_VALUE).build();
            set(key, icardinality);
        } else {
            icardinality = new LinearCounting(fireMapByte);
        }
        return icardinality;
    }

    private void set(String key, ICardinality icardinality) {
        try {
            fireMap.put(key, icardinality.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

redis 集合

【热门文章】
【热门文章】