
最先想到的就是集合了, 每来一个ID就放Set里, 执行SADD, 然后统计UV的时候直接SCARD.问题来了。假设我们运营的是一个大型网站每天有10亿的独立访客而且每个用户ID是一个64位的字符串比如一个很长的设备指纹或者加密后的标识。如果用Set来存我们算一下内存每个用户ID64位意味着大约8到16个字节。再加上Redis存储元素时的额外开销比如指针、哈希表节点、SDS结构等每个元素实际占用可能到30-50字节。10亿 × 40字节 ≈ 40GB。这显然是不现实的。即使我们退一步把用户ID当成普通的短字符串比如10个字符10亿 × (10 开销) 也轻松超过 12GB。有资料直接指出标准Set存储10亿个唯一用户ID需要大约12GB RAM。那有人可能会想能不能拆分成多个桶比如我们按用户ID的哈希值把它拆成1万个桶每个桶平均10万个元素。这样每个桶的大小就降下来了。算一下10万 × 40字节 ≈ 4MB。但这不是关键关键在于统计操作。要统计总UV我们需要遍历这1万个桶对每个桶执行 SCARD然后把结果加起来。1万次 SCARD 操作虽然单个很快但加起来就是1万次网络往返或者1万次命令执行。如果这些命令都在Redis的主线程上串行执行整个统计过程可能阻塞Redis几秒钟。对于一个在线服务来说这是不可接受的。所以Set方案在这个量级下内存和性能都会崩溃。有没有更优的方案, 底层原理是什么?