1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| 1. HyperLogLog:(UV) 优势:无需存储完整用户 ID,通过概率算法估算基数(去重后的数据量),内存占用极小。 特点:误差率约 0.8%,完全满足运营统计需求;100 万用户仅需约 12KB 内存。
2. Bitmap:(DAU) 优势:用二进制位表示用户状态(0 = 不活跃,1 = 活跃),存储空间极致压缩。 特点:精确去重;100 万用户仅需约 125KB(1000000/8/1024≈0.122MB)内存。
3. UV统计: @Service public class UVStatService {
@Resource private RedisTemplate<String, Object> redisTemplate;
private static final String UV_KEY_PREFIX = "uv:"; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
public void recordVisit(String userId) { String key = UV_KEY_PREFIX + LocalDate.now().format(DATE_FORMATTER); redisTemplate.opsForHyperLogLog().add(key, userId); }
public long getTodayUV() { String key = UV_KEY_PREFIX + LocalDate.now().format(DATE_FORMATTER); return redisTemplate.opsForHyperLogLog().size(key); }
public long getUVByDate(LocalDate date) { String key = UV_KEY_PREFIX + date.format(DATE_FORMATTER); return redisTemplate.opsForHyperLogLog().size(key); }
public long mergeUV(LocalDate startDate, LocalDate endDate) { String tempKey = "uv:merge:" + System.currentTimeMillis(); while (!startDate.isAfter(endDate)) { String key = UV_KEY_PREFIX + startDate.format(DATE_FORMATTER); redisTemplate.opsForHyperLogLog().union(tempKey, key); startDate = startDate.plusDays(1); } long total = redisTemplate.opsForHyperLogLog().size(tempKey); redisTemplate.delete(tempKey); return total; } }
4. DAU统计 @Service public class DAUStatService {
@Resource private RedisTemplate<String, Object> redisTemplate;
private static final String DAU_KEY_PREFIX = "dau:"; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
public void recordActive(Long userId) { String key = DAU_KEY_PREFIX + LocalDate.now().format(DATE_FORMATTER); redisTemplate.opsForValue().setBit(key, userId, true); }
public long getTodayDAU() { String key = DAU_KEY_PREFIX + LocalDate.now().format(DATE_FORMATTER); return redisTemplate.opsForValue().bitCount(key); }
public long getDAUByDate(LocalDate date) { String key = DAU_KEY_PREFIX + date.format(DATE_FORMATTER); return redisTemplate.opsForValue().bitCount(key); }
public boolean isActiveToday(Long userId) { String key = DAU_KEY_PREFIX + LocalDate.now().format(DATE_FORMATTER); return redisTemplate.opsForValue().getBit(key, userId); }
public void archiveMonthly(LocalDate month) { String archiveKey = "dau:archive:" + month.format(DateTimeFormatter.ofPattern("yyyyMM")); LocalDate firstDay = month.withDayOfMonth(1); LocalDate lastDay = month.plusMonths(1).withDayOfMonth(1).minusDays(1); while (!firstDay.isAfter(lastDay)) { String dailyKey = DAU_KEY_PREFIX + firstDay.format(DATE_FORMATTER); redisTemplate.getConnectionFactory().getConnection() .bitOp( BitOp.OR, archiveKey.getBytes(), dailyKey.getBytes() ); firstDay = firstDay.plusDays(1); } } }
|