需求
平常都会有个需求统计:计算日活、7日活、月活数据 记录IP
SET方式
@Autowired
private RedisCacheService redisCacheService;
private final static String key = "USER_COUNT";
/**
* 假如用户有100万,set存储100万个id号,如果一个id号占32个字节,
* 总共就是差不多32M,一个月就是960M 差不多一个G了
* 数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,
* 某用户是否在线状态 : setbit 非常的牛逼的功能
*/
@Test
public void user_sadd() {
String midKey = new DateTime().toString(Constant.DATE_C_YMD);
redisCacheService.delete(key);
for (int k = 0; k < 10; k++) {
redisCacheService.sAdd(key, midKey + k);
}
long ksize = redisCacheService.sSize(key);
System.out.println(" size : " + ksize);
//result : 10
}
BitMap
/**
* 100万个id号需要100万个bit位,也就是100万/8 = 125K字节 存储100万个用户,
* 只需要125K个字节,一个月才4M
*/
@Test
public void test_sbit() {
redisCacheService.delete(key);
//====================登录========================
for (int k = 0; k < 10; k++) {
redisCacheService.setBit(key, k, Boolean.TRUE);
}
long ksize = redisCacheService.bitCount(key);
System.out.println("No1 . size : " + ksize);
List<Integer> list = Lists.newArrayList(1, 5, 7);
list.forEach(x->{
Boolean num = redisCacheService.getBit(key, x);
System.out.println(" id : " + x + ((num) ? "在线" : "不在线"));
});
//====================操作后========================
for (int k = 6; k < 9; k++) {
redisCacheService.setBit(key, k, Boolean.FALSE);
}
ksize = redisCacheService.bitCount(key);
System.out.println("No2 . size : " + ksize);
list = Lists.newArrayList(1, 5, 7);
list.forEach(x->{
Boolean num = redisCacheService.getBit(key, x);
System.out.println(" id : " + x + ((num) ? "在线" : "不在线"));
});
// No1 . size : 10
// id : 1在线
// id : 5在线
// id : 7在线
// No2 . size : 7
// id : 1在线
// id : 5在线
// id : 7不在线
}
BitCount代码
/**
* 统计bit位为1的总数
* @param key
*/
@Override
public long bitCount(final String key) {
return redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
long result = 0;
result = connection.bitCount(key.getBytes());
return result;
}
});
}
HyperLoglog
/**
* 存储100万个独立用户只需要15K左右,一个月才480K左右
*
* 鉴于 HyperLogLog 不保存数据内容的特性,所以,它只适用于一些特定的场景。
* 我这里给出一个最常遇到的场景需要:计算日活、7日活、月活数据 记录Ip
*
* 在 Redis 中每个键占用的内容都是 12K,理论存储近似接近 2^64 个值,
* 不管存储的内容是什么。这是一个基于基数估计的算法,只能比较准确的估算出基数,
* 可以使用少量固定的内存去存储并识别集合中的唯一元素。但是这个估算的基数并不一定准确,
* 是一个带有 0.81% 标准错误(standard error)的近似值
*
* 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。
* 这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
*/
@Test
public void test_HyperLog() {
String midKey = new DateTime().toString(Constant.DATE_C_YMD);
redisCacheService.delete(key);
for (int k = 0; k < 10; k++) {
redisCacheService.pfAdd(key, midKey + k);
}
long ksize = redisCacheService.pfSize(key);
System.out.println(" size : " + ksize);
// size : 10
}
HyperLoglog相关工具类
@Override
public Long pfAdd(String key, String... values) {
HyperLogLogOperations operations = redisTemplate.opsForHyperLogLog();
return operations.add(key, values);
}
@Override
public Long pfSize(String key) {
HyperLogLogOperations operations = redisTemplate.opsForHyperLogLog();
return operations.size(key);
}