(高阶) Redis 7 第14讲 数据统计分析 实战篇

本文详细讲解了如何使用Redis进行亿级数据的统计分析,包括聚合统计、排序统计、二值统计和基数统计。重点介绍了HyperLogLog数据结构在独立访客(UV)、页面浏览量(PV)、日活跃用户(DAU)和月活跃用户(MAU)等场景的应用,并给出了淘宝网首页亿级UV统计的解决方案。此外,还探讨了GEO命令在地理位置数据处理中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面试题

存得进,取得出,反应快抖音电商商品评论:排序+展现+取前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统计方案

需求

  1.  UV的统计需要去重,一个用户一天内的多次访问只能算作一次
  2. 淘宝、天猫首页的UV,平均每天是1~1.5个亿左右
  3. 每天存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

命令 

  1.  GEOADD 添加经纬度坐标

                GEOADD key longitude latitude member [longitude latitude member

  1. GEOPOS 返回经纬度

                GEOPOs key member [member ...]

  1. GEOHASH 返回坐标的geohash表示

                GEOHASH key member [member ...]

  1. GEODIST 两个位置之间距离

                GEODIST key member1 member2 [m|kmlftmi]

  1. GEORADIUS
  2. 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 数据统计分析,如有问题请指教。
 
🌹🌹 如你对技术也感兴趣,欢迎交流。
 
🌹🌹🌹  如有需要,请👍点赞💖收藏🐱‍🏍分享 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一介布衣+

做好事,当好人

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值