Redis之bitmap/hyperlog/GEO
- 一 面试题引入
- 二 统计的类型
- 三 hyperloglog
- 3.1 行业术语
- 3.2 hyperloglog基础
- 3.2.1 基数
- 3.2.2 定义
- 3.2.3 基数统计
- 3.2.4 基本命令
- 3.3 HyperLogLog原理
- 3.3.1 去重复统计的方式
- 3.3.2 原理
- 3.4 HyperLogLog案例实战
- 3.4.1 需求
- 3.4.2 方案讨论
- 3.4.3 HyperLogLogService
- 四 GEO
- 4.1 面试题引入
- 4.2 redis GEO 基础命令
- 4.3 案例
- 4.3.1 关键点
- 4.3.2 代码实战
- 五 bitmap
- 5.1 面试题引入
- 5.2 定义
- 5.3 作用
- 5.4 基础命令
一 面试题引入
- 抖音电商直播,主播介绍的商品有评论,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的偏移量时从零开始算的.