面试题
存得进,取得出,反应快 | 抖音电商商品评论:排序+展现+取前10条 |
用户使用手机APP签到打卡:1天对应一系列用户签到记录,新浪微博/钉钉打卡,如何进行统计 | |
页面访问点击量:一个网页对应一系列的访问点击,淘宝网首页,多少人浏览首页 | |
公司系统上线后,UV、PV、DUV是什么 |
亿级统计
聚合统计
统计多个元素集合结果,也即交集差集并集
A =abc12 B=123ax
A - B: 属于A但不属于B的元素构成集合(SDIFF key [key...])
A∪B : 属于A或属于B的元素构成集合(SUNION key [key ....])
A∩B : 属于A同时属于B的元素构成集合(SINTER key [key ....])(SINTERCARD numkeys key [key ....] [LIMIT limit])
排序统计
排序+展现+取前10条 ZSET
ZADD comments 1981392148 v1 1981392149 v2 1981392158 v3
ZRANGE comments 0 2
ZREVREANGE comments 0 2
ZRANGEBYSCORE comments 1981392148 1981392158
ZRANGEBYSCORE comments 1981392148 1981392158 limit 0 5
二值统计
集合元素的取值只有0 或 1 两种 bitmap
常见打卡,是否签到
基数统计
统计一个集合中不重复的元素个数(HyperLogLog)
HyperLogLog
UV(Unique Visitor)
独立访客,一般理解为客户端IP(需要考虑去重)
PV(Page View)
客户浏览量(不用去重)
DAU(Daily Active User)
日活跃用户量(登录或者使用产品的用户数(去重复登录的用户))
常用于反映网站、互联网应用或者网络游戏的运营情况
MAU(Month Active User)
月活跃用户量
案例
统计单日一个页面的访问量(PV),单次访问就算一次。
统计单日一个页面的用户访问量(UV),即按照用户为维度计算,单个用户一天内多次访问也只算一次。
多个key的合并统计,某个门户网站的所有模块的PV聚合统计
插入 PFADD key v1 v2 v3 v4....
统计 PFCOUNT key
合并 PFMERGE DISTRESULT hello1 hello2
合并后统计 PFCOUNT DISTRESULT
HASHSET(Java)
BitMap(不适合亿级统计)
HyperLogLog :概率算法
通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下可以使用,因为概率算法不直接存储数据本身,通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,又不储存数据可以节约内存。
原理
只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
有误差:牺牲准确率来换取空间,误差率仅 0.81% 左右(来源)
淘宝网首页亿级UV的Redis统计方案
需求
- UV的统计需要去重,一个用户一天内的多次访问只能算作一次
- 淘宝、天猫首页的UV,平均每天是1~1.5个亿左右
- 每天存1.5个亿的IP,访问者来了后先去查是否存在,不存在加入
方案
mysql | |
Redis 的Hash 结构 | IPV4 最多15字节(IP="192.168.111.111") 某天 1.5亿*15字节=2G,一个月60G |
HyperLogLog | 在 Redis 里面,每个HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2464 个不同元素的基数 Redis使用了2^14=16384个桶,按照上面的标准差,误差为0.81%,精度相当高。Redis使用个ong型哈希值的前14个比特用来确定桶编号,剩下的50个比特用来做基数估计。而2^6=64.所以只需要用6个比特表示下标值,在一般情况下,一个HLL数据结构占用内存的大小为16384*6 /8 = 12kB,Redis将这种情况称为密集 (dense) 存储。 |
/**
* 模拟用户点击淘宝首页,每个用户不同ip
*/
@PostConstruct
public void initIP() {
new Thread(() -> {
String ip = null;
for (int i = 0; i < 200; i++) {
Random random = new Random();
ip = random.nextInt(256) + "." + random.nextInt(256)
+ "." + random.nextInt(256) + "." + random.nextInt(256);
Long hll = redisTemplate.opsForHyperLogLog().add("hll", ip);
log.info("IP={},访问首页的次数={}", ip, hll);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1").start();
}
public Long uv(){
return redisTemplate.opsForHyperLogLog().size("hll");
}
效果图
GEO
命令
- GEOADD 添加经纬度坐标
GEOADD key longitude latitude member [longitude latitude member
- GEOPOS 返回经纬度
GEOPOs key member [member ...]
- GEOHASH 返回坐标的geohash表示
GEOHASH key member [member ...]
- GEODIST 两个位置之间距离
GEODIST key member1 member2 [m|kmlftmi]
- GEORADIUS
- GEORADIUSBYMEMBER
GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定
案例
美团APP附加的酒店
public String geoAdd() {
Map<String, Point> map = new HashMap<>();
map.put("天安门", new Point(116.403963, 39.915119));
map.put("长城", new Point(116.746579, 40.482509));
map.put("故宫", new Point(116.403414, 39.924091));
redisTemplate.opsForGeo().add(CITY, map);
return map.toString();
}
public Point position(String member) {
//获取经纬度坐标
return (Point) redisTemplate.opsForGeo().position(CITY, member).get(0);
}
public String hash(String member) {
//geohash算法生成的base32编码值
return (String) redisTemplate.opsForGeo().hash(CITY, member).get(0);
}
public Distance distance(String member1, String member2) {
// 查询距离
return redisTemplate.opsForGeo().distance(CITY, member1, member2, RedisGeoCommands.DistanceUnit.KILOMETERS);
}
public GeoResults radiusByxy() {
//查询附加
Circle circle = new Circle(116.403414, 39.924091, Metrics.KILOMETERS.getMultiplier());
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortDescending().limit(5);
return redisTemplate.opsForGeo().radius(CITY, circle, args);
}
public GeoResults radiusByMember() {
// 通过地方查询附近
Distance distance = new Distance(1000, RedisGeoCommands.DistanceUnit.KILOMETERS);
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortDescending().limit(5);
return redisTemplate.opsForGeo().radius(CITY, "长城", distance, args);
}
🌹 以上分享 Redis 数据统计分析,如有问题请指教。
🌹🌹 如你对技术也感兴趣,欢迎交流。
🌹🌹🌹 如有需要,请👍点赞💖收藏🐱🏍分享