网站现在有大量用户,用户的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 集合