Redis之bitmap/hyperloglog/GEO

一 面试题引入

  • 抖音电商直播,主播介绍的商品有评论,1个商品对应1系列的评论,排序+展现+提取10条记录。
  • 用户在手机APP上的签到打卡信息,1天对应1系列哟冰壶的签到记录,钉钉打卡签到,来没来如何统计?
  • 应用网站上的网页访问信息:1个网页对应1系列的访问点击,淘宝网首页,每天有多少人浏览首页?
  • 公司系统上线后,说一下UV、PV、DAU分别是多少?
  • 对集合中数据进行统计:
    • 在移动应用中,需要统计每天的新增用户数和第2天的留存用户数。
    • 在电商网站的商品评论中,需要统计评论表中的最新评论。
    • 在签到打卡中,需要统计一个月内连续打卡的用户数。
    • 在网页访问记录中,需要统计独立访客(Unique Visitor,VU)量
    • 痛点: 类似今日头条、抖音、淘宝这些的用户访问级别都是亿级的,请问如何处理?

二 统计的类型

在亿级系统中,常见的统计有四种

  • 聚合统计:统计多个集合元素的聚合结果(交差并等集合统计)在这里插入图片描述

  • 排序统计:抖音短视频最新评论留言的场景,请你设计一个展现列表。考察对数据结构的理解及设计思路

    • zset: 在这里插入图片描述

    • 在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用Zset。

  • 二值统计:集合元素的取值就只有0和1两种,在钉钉上班签到打卡的场景中,我们只用记录有签到(1)或没签到(0)。(bitmap)

  • 基数统计:指统计一个集合中不重复的元素个数。(hyperloglog)

三 hyperloglog

3.1 行业术语

  • UV:Unique Visitor,独立访客,一般理解为客户端IP。需要去重考虑
  • PV:Page View,页面浏览量。
  • DAU:Daily Active User,日活跃用户量。登录或者使用了某个产品的用户数(去重复登录的用户),常用户反映网站、互联网应用或者网络游戏的运营情况。
  • MAU:Monthly Active User,月活跃用户量。

3.2 hyperloglog基础

3.2.1 基数

是一种数据集,去重复后的真实个数。

3.2.2 定义

Redis Hyperloglog是用来做基数统计的算法,hyperloglog的优点是,在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定的,并且是很小的。
在Redis里面,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog不能像集合那样,返回输入的各个元素。

3.2.3 基数统计

用于统计一个集合中不重复的元素个数,就是对集合去重后剩余元素的计算。

3.2.4 基本命令

在这里插入图片描述

3.3 HyperLogLog原理

3.3.1 去重复统计的方式

  • HashSet

  • bitMap

    如果数据量较大亿级统计,使用bitmaps同样会有这个问题。
    bitmap是通过用位bit数组来表示各元素是否出现,每个元素对应一位,所需的总内存为N个bit。
    基数基数则将每一个元素对应的bit数组中的其中一位,比如bit数组010010101(按照从零开始下标,有的就是1、4、6、8)。
    新进入的元素只需要将已经有的bit输入和新加入的元素进行按位或计算就行。这个方式能大大减少内存占用且位操作迅速。
    But, 假设一个样本案例就是一亿个基数位值数据,一个样本就是一亿。
    如果要统计1亿个数据的基数位值,大约需要内存100000000/8/1024/1024约等于12M,内存减少占用的效果显著。这样得到统计一个对象样本的基数值需要12M。
    如果统计10000个对象样本,就需要117.18G将近120G,可见使用bitmaps还是不适用大数据量下(亿级)的基数计数场景。
    bitmaps方法是精确计算的。

  • 上述小结

    样本元素越多内存消耗急剧增大,难以管控+各种慢,对于亿级统计不太合适。

  • 解决方案:概率算法

    通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下可以使用。因为概率算法不直接存储数据本身,通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,由于又不存储数据故此可以大大节约内存。
    HyperLogLog就是一种概率算法的实现。

3.3.2 原理

HyperLogLog只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
有误差: HyperLogLog提供不精确的去重计数方案,牺牲准确率来换取空间,误差仅仅只是0.81%左右。

3.4 HyperLogLog案例实战

3.4.1 需求

  • UV的统计需要去重,一个用户一天内的多次访问只能算作一次。
  • 淘宝、天猫首页的UV,平均每天是1~1.5个亿左右。
  • 每天存1.5个亿的IP。访问者来了先去查是否存在,不存在加入。

3.4.2 方案讨论

  • 用mysql (o(╥﹏╥)o)

  • 用redis的hash结构存储

    redis — hash = <keyDay, <ip,1>>
    按照ipv4的结构来说明,每个ipv4的地址最多是15个字节,某一天的1.5亿*15个字节 = 2G,一个月60G,存储量过大。

  • hyperLogLog:在这里插入图片描述

3.4.3 HyperLogLogService

@Service
public class HyperLogLogService{

	@Resource
	private RedisTemplate redisTemplate;
	
	//模拟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) ;
				
				redisTemplate.opfForHyperLogLog().add("hll",ip);

				//模拟暂停3秒
				try{
					TimeUnit.SECODNS.sleep(3);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		},"t1").start();

	}

	//获取UV
	public long uv(){
		return redisTemplate.opsForHyperLogLog().size("hll");
	}

}

四 GEO

4.1 面试题引入

移动互联网时代LBS应用越来越多,交友软件中附件的人、外卖软件中附件的美食商铺、打车软件附件的车辆等等。那这种附件各种形形色色的XXX地址位置选择是如何实现的?
会存在什么问题呢?

  • 查询性能问题,如果并发高,数据量大这种查询是要搞垮mysql数据库的。
  • 一般mysql查询的是一个平面矩形访问,而轿车服务是以我为中心N公里为半径的圆形覆盖。
  • 精准度问题,我们知道地球不是平面坐标系,而是一个圆球,这种矩形计算在长距离计算时会有很大误差,mysql不合适。

4.2 redis GEO 基础命令

  • GEOADD :添加经纬度坐标
  • GEOPOS:返回经纬度
  • GEOHASH:返回坐标的GEOHASH表示
  • GEODIST:两个位置之间距离
  • GEORADIUS:以半径为中心,查找附近的XXX
    在这里插入图片描述
  • GEORADIUSBYMEMBER:

4.3 案例

4.3.1 关键点

GEORADIUS:以给定的经纬度为中心,找出某一半径内的元素。

4.3.2 代码实战

GeoController

@RestController
public class GeoController{

	@Resource
	private GeoService geoService;

	//添加经纬度坐标
	@GetMapping("/geoadd")
	public String geoAdd(){
		return geoService.getAdd();
	}
	//获取经纬度坐标geopos
	@GetMapping("/geopos")
	public Point position(String member){
		return geoService.position(member);
	}

	//获取经纬度生成的base32编码值geohash
	@GetMapping("/geohash")
	public Point hash(String member){
		return geoService.hash(member);
	}

	//获取经纬度生成的base32编码值geohash
	@GetMapping("/geohash")
	public Point hash(String member){
		return geoService.hash(member);
	}

	//获取给定两个位置之间的距离
	@GetMapping("/geodist")
	public Distance distance(String member1,String memeber2){
		return geoService.distance(member1,member2);
	}

	//通过经纬度获取在某个固定点附件的位置(位置写死)
	@GetMapping("/georadius")
	public GeoResults radiuByxy(){
		return geoService.radiuByxy();
	}
}

GeoService

@Service
public class GeoService{
	
	public static final String CITY = "city";
	
	@Autowired
	private RedisTemplate redisTemplate;

	public String geoAdd(){
		Map<String,Point> map = new HashMap<>();
		map.put("天安门",new Point(116.41338 , 39.91092 ));
		map.put("故宫",new Point(116.40341 , 39.92409 ));
		map.put("长城",new Point(116.02407 , 40.36264 ));				
		redisTemplate.opsForGeo().add(CITY,map);
		return map.toString();
	}

	public Point position(String member){
		List<Point> list = redisTemplate.opsForGeo().position(CITY,member);
		return list.get(0);
	}

	public Point hash(String member){
		List<String> list = redisTemplate.opsForGeo().hash(CITY,member);
		return list.get(0);
	}

	public Distance distance(String member1,String memeber2){
		Distance distance = redisTemplate.opsForGeo().distance(CITY,member1,member2,
			RedisGeoCommands.DistanceUnit.KILOMETERS);
		return distance;
	}

	public GeoResults radiuByxy(){
		//经纬度:116.40048 , 39.91680  中山公园
		Circle circle = new Circle(116.40048 , 39.91680,Metrics.KILMETERS.getMultiplier());
		//返回50条
		RedisGeoCommands.GeoRadiusCommandArgs args=RedisGeoCommands.GeoRadiusCommandArgs
			.newGeoRadiusArgs().includeDistance()
			.includeCoordinates().sortDescending().limit(50);
		GeoResults redius = redisTemplate.opsForGeo().radius(CITY,circle,args)
		return redius;
	}
}

五 bitmap

5.1 面试题引入

  • 日活统计
  • 连续签到打卡
  • 最近一周的活跃用户
  • 统计指定用户一年之中的登录天数
  • 用户按照一年365天,哪几天登录过,那几天没有登录,全年中登录的天数共计多少?

5.2 定义

由0和1状态表现的二进制位的bit数组。
在这里插入图片描述
用String类型作为底层数据结构实现的一种统计二值状态的数据类型。
位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们可以称之为一个索引或者位格)。Bitmap支持的最大位数是2^32位,它可以极大的节约存储空间,使用512内存就可以存储多达42.9亿的字节信息。

5.3 作用

用于状态统计,Y、N,类似AtomicBoolean。

  • 用户是否登录过Y、N
  • 电影、广告是否被点击播放过
  • 钉钉打卡上下班,签到统计

5.4 基础命令

Bitmap的偏移量时从零开始算的.
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值