redis大数据统计之hyperloglog,GEO,Bitmap

目录

一、亿级系统常见的四中统计

1、聚合统计

2、排序统计

3、二值统计

4、基数统计

二、hyperloglog

去重的方式有哪些?

hyperloglog实战演示

1、技术选型

2、代码演示 

三、GEO

GEO实战演示

四、Bitmap


一、亿级系统常见的四中统计

1、聚合统计

聚合统计:统计多个集合元素的聚合结果,交并差集和聚合函数的应用。用于推广社交

2、排序统计

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

3、二值统计

二值统计:用于签到打卡,使用bitmap

4、基数统计

基数统计:指统计一个集合中不重复的元素个数,使用hyperloglog

统计名词

UV:Unique Visitor 独立访客,一般伟客户端IP(需要去重)

PV:Page View 页面浏览量(不去重)

DAU:Daily Active User 日活跃用户,登录或者使用了某个产品的用户数(去除重复登录的用户)

常用于反映网站、互联网应用或者网络游戏的运营情况。

MAU:Monthly Active User 月活跃用户

二、hyperloglog

需求分析

统计单日一个页面的访问量(PV),单次访问就算一次。

统计单日一个页面的用户访问量(UV),即按照用户为维度计算,单个用户一天内多次访问也只算一次。

多个key的合并统计,某个门户网站的所有模块的PV聚合统计就是整个网站的总PV。

去重的方式有哪些?

1、HashSet

2、bitmap

bitmap是通过用位bit数组来表示各元素是否出现,每个元素对应一位,所需的总内存为N个bit。
基数计数则将每一个元素对应到bit数组中的其中一位,比如bit数组010010101(按照从零开始下标,有的就是1、4、6、8)。新进入的元素只需要将已经有的bit数组和新加入的元素进行按位或计算就行。这个方式能大大减少内存占用且位操作迅速。但是对于亿级统计不太合适。

3、hyperloglog

只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。Hyperloglog

提供不精确的去重计数方案,牺牲准确率来换取空间,误差仅仅只是0.81%左右。

hyperloglog实战演示

示例:淘宝网站首页亿级UV的Redis统计方案

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

1、技术选型

使用mysql

        mysql扛不住稍微大一点的并发,而且都需要存入mysql中,导致mysql的检索也会变慢

使用redis的hash结构存储

        按照ipv4的结构来说明,一个ip最多15个字节(ip=“192.168.238.1xx”),某一天 1.5亿*15个字节 = 2G,一个月60G,内存直接没了,加内存条都没用

使用hyperloglog

        在Redis里面,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2的64次方个不同元素的基数。

2、代码演示 

HypeLogLogService

public interface HypeLogLogService {public long uv();
}

HyperLogLogServiceImpl

import com.xfcy.service.HypeLogLogService;import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.TimeUnit;@Slf4j
@Service
public class HypeLogLogServiceImpl implements HypeLogLogService {@Resourceprivate RedisTemplate redisTemplate;/*** 模拟后台有用户点击网站首页,每个用户来自不同的IP地址** @PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。*/@PostConstructpublic 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地址访问首页的次数={}",ip,hll);try {// 暂停3秒钟TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {throw new RuntimeException(e);}}}, "t1").start();}public long uv() {// PFCOUNTreturn redisTemplate.opsForHyperLogLog().size("hll");}
}

HyperLogLogController

import com.xfcy.service.HypeLogLogService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@Api(tags = "淘宝亿级UV的Redis统计方案")
@Slf4j
@RestController
public class HyperLogLogController {@Resourceprivate HypeLogLogService hypeLogLogService;@ApiOperation("获得IP去重复后的UV统计访问量")@RequestMapping(value = "/uv",method = RequestMethod.GET)public long uv(){return hypeLogLogService.uv();}
}

运行结果,统计每个ip访问的次数 

三、GEO

面试题

移动互联网时代LBS应用越来越多,交友软件中附近的小姐姐、外卖软件中附近的美食店铺、打车

软件附近的车辆等等。那这种附近各种形形色色的XXX地址位置选择是如何实现的?

mysql会有什么问题呢?

1.查询性能问题,如果并发高,数据量大这种查询会搞垮mysql数据库

2.一般mysql查询的是一个平面矩形访问,而叫车服务要以我为中心N公里为半径的圆形覆盖。I

3.精准度的问题,我们知道地球不是平面坐标系,而是一个圆球,这种矩形计算在长距离计算时会

有很大误差,mysql不合适。

GEO常用命令

GEOADD:添加经纬度坐标,该命令用于将一个或多个指定的地理位置(经纬度)添加到指定的

键中。其中,key 是存储位置信息的键,longitude 和 latitude 是经度和纬度,member 是与该位置

相关联的成员。

GEOADD key longitude latitude member [longitude latitude member ...]

GEOPOS:返回经纬度,该命令用于获取指定成员的经纬度坐标。其中,key 是存储位置信息的

键,member 是需要获取经纬度的成员。

GEOPOS key member [member ...]

GEOHASH:返回坐标的geohash表示,该命令用于获取指定成员的 geohash 表示。其中,key 是

存储位置信息的键,member 是需要获取 geohash 的成员。

GEOHASH key member [member ...]

GEODIST:两个位置之间的距离,该命令用于计算两个位置之间的距离。其中,key 是存储位置信息的键,member1 和 member2 是需要计算距离的两个成员,unit 是可选参数,用于指定距离的单位,默认为米。

GEODIST key member1 member2 [unit]

GEORADIUS:根据给定的经纬度获取范围内的位置 该命令用于根据给定的中心点经纬度和半径,在指定的键中获取范围内的位置信息。其中,key 是存储位置信息的键,longitude 和 latitude 是中心点的经纬度,radius 是半径,单位可以是 m(米)、km(千米)、ft(英尺)或 mi(英里),WITHCOORD、WITHDIST、WITHHASH 和 COUNT 是可选参数,用于指定返回结果的附加信息和数量限制。

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

GEORADIUSBYMEMDBER:根据给定的成员获取范围内的位置 该命令用于根据给定的成员,在指定的键中获取范围内的位置信息。其中,key 是存储位置信息的键,member 是指定的成员,radius 是半径,单位可以是 m(米)、km(千米)、ft(英尺)或 mi(英里),WITHCOORD、WITHDIST、WITHHASH 和 COUNT 是可选参数,用于指定返回结果的附加信息和数量限制。

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

GEO实战演示

美团地图位置附近的酒店推送

需求分析:美团附近酒店,附近的人,一公里以内的各种营业厅,饭店,附近共享单车等等。

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

GEOService


public interface GeoService {String geoAdd();Point position(String member);String hash(String member);Distance distance(String member1, String member2);GeoResults radiusByxy();GeoResults radiusMember();}

GEOServiceImpl

import com.xfcy.service.GeoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service
@Slf4j
public class GeoServiceImpl implements GeoService {public static final String CITY = "city";@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic String geoAdd() {Map<String, Point> map = new HashMap<>();map.put("白马寺", new Point(112.610356,34.728481));map.put("龙门石窟", new Point(112.484071,34.564375));map.put("老君山", new Point(111.663,33.75186));redisTemplate.opsForGeo().add(CITY, map);return null;}@Overridepublic Point position(String member) {// 获取经纬度坐标List<Point> position = redisTemplate.opsForGeo().position(CITY, member);return position.get(0);}@Overridepublic String hash(String member) {// geohash 算法生成的base32编码List<String> hash = redisTemplate.opsForGeo().hash(CITY, member);return hash.get(0);}@Overridepublic Distance distance(String member1, String member2) {// 获取两个给定位置之间的距离Distance distance = redisTemplate.opsForGeo().distance(CITY, member1, member2,RedisGeoCommands.DistanceUnit.KILOMETERS);return distance;}@Overridepublic GeoResults radiusByxy() {// 通过经纬度查找附近的,白马寺的位置 112.610356,34.728481Circle circle = new Circle(112.610356, 34.728481, Metrics.KILOMETERS.getMultiplier());// 返回50条RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().sortDescending().limit(50);GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = redisTemplate.opsForGeo().radius(CITY, circle, args);return geoResults;}@Overridepublic GeoResults radiusMember() {// 通过地方查找附近,洛阳白马寺为例String member = "白马寺";// 返回10条RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().sortAscending().limit(10);// 半径 10 公里内Distance distance = new Distance(10, Metrics.KILOMETERS);GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = redisTemplate.opsForGeo().radius(CITY, member, distance, args);return geoResults;}
}

GEOController

import com.xfcy.service.GeoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import org.springframework.data.geo.Point;@Api(tags = "美团地图位置附近的酒店推送GEO")
@Slf4j
@RestController
public class GeoController {@Resourceprivate GeoService geoService;@ApiOperation("添加经纬度坐标")@RequestMapping(value = "/geoadd",method = RequestMethod.GET)public String geoAdd() {return geoService.geoAdd();}@ApiOperation("获取经纬度坐标geopos")@RequestMapping(value = "/geopos",method = RequestMethod.GET)public Point position(String member){return geoService.position(member);}@ApiOperation("获取经纬度生成的base32编码值geohash")@RequestMapping(value = "/geohash",method = RequestMethod.GET)public String hash(String member){return geoService.hash(member);}@ApiOperation("获取两个给定位置之间的距离")@RequestMapping(value = "/geodist",method = RequestMethod.GET)public Distance distance(String member1, String member2){return geoService.distance(member1,member2);}@ApiOperation("通过经纬度查找洛阳白马寺附近的")@RequestMapping(value = "/georadius",method = RequestMethod.GET)public GeoResults radiusByxy(){return geoService.radiusByxy();}@ApiOperation("通过地方查找附近,白马寺为例")@RequestMapping(value = "/georadiusByMember",method = RequestMethod.GET)public GeoResults radiusMember(){return geoService.radiusMember();}}

四、Bitmap

Bitmap:由 0 和 1 状态表现得二进制位的 bit 数组

作用:日活统计,连续签到打卡,近一周活跃用户,统计用户一年登录天数,电影、广告是否被点击播放过。

案例:京东签到领取京东

1、小厂方法,传统 mysql 方式

CREATE TABLE user_sign
(keyid BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,user_key VARCHAR(200),#京东用户IDsign_date DATETIME,#签到日期(20210618)sign_count INT #连续签到天数
)INSERT INTO user_sign(user_key,sign_date,sign_count)
VALUES ('20210618-xxxx-xxxx-xxxx-xxxxxxxxxxxx','2020-06-18 15:11:12',1);SELECTsign_count
FROMuser_sign
WHEREuser_key = '20210618-xxxx-xxxx-xxxx-xxxxxxxxxxxx'AND sign_date BETWEEN '2020-06-17 00:00:00' AND '2020-06-18 23:59:59'
ORDER BYsign_date DESCLIMIT 1;

问题:签到量用户小这个可以,用户量很大就会出现问题

如何解决?

  • 一条签到记录对应一条记录,会占据越来越大的空间。

  • 一个月最多31天,刚好我们的int类型是32位,那这样一个int类型就可以搞定一个月,32位大于31天,当天来了位是1没来就是0。

  • 一条数据直接存储一个月的签到记录,不再是存储一天的签到记录。

大厂方法

  • 基于redis的 Bitmap 实现签到日历

  • 在签到统计时,每个用户一天的签到用1个bit位就能表示,

  • 一个月(假设是31天)的签到情况用31个bit位就可以,一年的签到也只需要用365个bit位,根本不用太复杂的集合类型

基本命令

SETBIT key offset value    // 将第offset的值设为value  value只能是0或1  offset 从0开始
GETBIT key offset        // 获得第offset位的值
STRLEN key              // 得出占多少字节 超过8位后自己按照8位一组一byte再扩容
BITCOUNT key         // 得出该key里面含有几个1
BITOP and destKey key1 key2 // 对一个或多个 key 求逻辑并,并将结果保存到 destkey 
BITOP or destKey key1 key2 // 对一个或多个 key 求逻辑或,并将结果保存到 destkey 
BITOP XOR destKey key1 key2 // 对一个或多个 key 求逻辑异或,并将结果保存到 destkey 
BITOP NOT destKey key1 key2 // 对一个或多个 key 求逻辑非,并将结果保存到 destkey 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/669323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32F407移植OpenHarmony笔记7

继上一篇笔记&#xff0c;成功启动了liteos_m内核&#xff0c;可以创建线程了&#xff0c;也能看到shell控制台了。 今天研究文件系统&#xff0c;让控制台相关文件命令如mkdir和ls能工作。 liteos_m内核支持fatfs和littlefs两个文件系统&#xff0c; fatfs适用于SD卡&#xff…

将11.x.x升级至16.x.x不成功的一系列问题(二)node-sass sass-loader需安装指定版本

nvm 版本切换搞定了 咱就是说 那个node-sass好像有点毛病 还得指定对应的loaber版本 node.js 16.18.1对应的如下 “node-sass”: “^6.0.1”, “sass-loader”: “^10.0.1”, node.js 11.8.0 对应的如下 “node-sass”: “^4.14.1”, “sass-loader”: “^7.3.1”, 老项目即…

python-题库篇-数学

文章目录 求最大公约数和最小公倍数斐波那契数列求和运算求前n阶乘的和求年龄 求最大公约数和最小公倍数 两个数的最大公约数是两个数的公共因子中最大的那个数&#xff1b;两个数的最小公倍数 则是能够同时被两个数整除的最小的那个数。 输入&#xff1a;&#xff08;120 和…

零基础如何入门渗透测试2024年最新版,保姆级教程,小白必看!

转眼间&#xff0c;从大三开始学安全&#xff0c;到现在也有五年了&#xff0c;也算是对渗透测试有一定理解&#xff0c;今天也是出一篇入门教程&#xff0c;以实操为主&#xff0c;希望可以帮助到想入门渗透测试的小白。如果觉得有用&#xff0c;可以给我点个赞和收藏&#xf…

Android: 深入理解 ‘companion object {}‘

Android: 深入理解 ‘companion object {}’ Kotlin是一种现代的、静态类型的编程语言&#xff0c;它在设计时充分考虑了开发者的生产力和代码的可读性。其中一个独特的特性就是companion object。在本篇博客中&#xff0c;我们将深入探讨这个特性&#xff0c;理解它的工作原理…

Java21 + SpringBoot3集成七牛云对象存储OSS,实现文件上传

文章目录 前言实现步骤引入maven依赖修改配置文件创建七牛云配置类创建文件操作服务类创建文件操作控制器前端实现运行效果 总结 前言 近日心血来潮想做一个开源项目&#xff0c;目标是做一款可以适配多端、功能完备的模板工程&#xff0c;包含后台管理系统和前台系统&#xf…

EOF和0区别

题目描述 KiKi学习了循环&#xff0c;BoBo老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“*”组成的X形图案。 输入描述&#xff1a; 多组输入&#xff0c;一个整数&#xff08;2~20&#xff09;&#xff0c;表示输出的行数&#xff0c;也表示组成“X”的反斜…

你的歌声婉转入云霄

可爱的一朵玫瑰花 - 吕继宏 可爱的一朵玫瑰花塞地玛丽亚 可爱的一朵玫瑰花塞地玛丽亚 那天我在山上打猎骑着马&#xff08;人善被人欺马善被人骑&#xff09; 正当你在山下歌唱婉转入云霄 歌声使我迷了路 我从山坡滚下 哎呀呀 你的歌声婉转入云霄 强壮的青年哈萨克伊万杜达尔 …

【八大排序】选择排序 | 堆排序 + 图文详解!!

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C语言进阶之路 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 一、选择排序1.1 基本思想1.2 算法步骤 动图演示1.3 代码实现1.4 选择排序特性总结 二…

C/C++内存管理的底层调用逻辑

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

【TCP/IP】用户访问一个购物网站时TCP/IP五层参考模型中每一层的功能

当用户访问一个购物网站时&#xff0c;网络上的每一层都会涉及不同的协议&#xff0c;具体网络模型如下图所示。 以下是每个网络层及其相关的协议示例&#xff1a; 物理层&#xff1a;负责将比特流传输到物理媒介上&#xff0c;例如电缆或无线信号。所以在物理层&#xff0c;可…

vue3项目实现预览图片、旋转图片功能

一、需求&#xff1a; 在点击图片时&#xff0c;能预览大图&#xff0c;弹出一个包含旋转图片功能按钮的弹窗。用户可通过点击按钮实现对图片的旋转操作 二、思路&#xff1a; 点击图片预览&#xff1a; 用户通过点击图片触发预览功能。接收图片的 URL&#xff0c;弹出一个模…

【GameFramework框架】四、GameFramework框架内置模块

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a; https://blog.csdn.net/q7…

问题:下列关于海关统计项目的表述,正确的有:A.进出境货物的统计重量和数量应以报关单位申报的重量和数 #笔记#职场发展#媒体

问题&#xff1a;下列关于海关统计项目的表述&#xff0c;正确的有&#xff1a;A&#xff0e;进出境货物的统计重量和数量应以报关单位申报的重量和数 下列关于海关统计项目的表述&#xff0c;正确的有&#xff1a; A&#xff0e;进出境货物的统计重量和数量应以报关单位申报的…

echarts使用之地图(五)

1 基本使用 百度地图 API : 使用百度地图的 api , 它能够在线联网展示地图 , 百度地图需要申请 ak 矢量地图 : 可以离线展示地图 , 需要开发者准备矢量地图数据。本文使用该方式。 json格式的数据如下&#xff1a; 格式参照&#xff1a;GeoJSON <!DOCTYPE html&…

Java实现数据可视化的智慧河南大屏 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…

泰克示波器(TBS2000系列)保存功能使用

目录 1.1 Save/Recall按钮1.2 保存动作1.3 文件格式1.4 保存 在使用示波器时&#xff0c;测量后的结果我们常常需要记录下来&#xff0c;大部分情况我们是拍照记录&#xff0c;单图像往往不清晰&#xff0c;这时使用示波器专用的保存功能&#xff0c;插入U盘即可保存&#xff0…

STM32--SPI通信协议(3)SPI通信外设

前言 硬件SPI&#xff1a;通过硬件电路实现&#xff0c;所以硬件SPI速度更快&#xff0c;有专门的寄存器和 库函数 &#xff0c;使用起来更方便。 软件SPI&#xff1a;也称模拟SPI&#xff0c;通过程序控制IO口电平模拟SPI时序实现&#xff0c;需要程序不断控制IO电平翻转&am…

JAVA字节流的两个重要子类FileOutputStream、FileInputStream

字节流&#xff0c;字符集&#xff0c;字符流 因为它们都是抽象类&#xff0c;所以要学习它们的子类 字节流的结构: FileOutputStream 操作本地文件的字节输出流&#xff0c;可以把程序中的数据写入本地文件。 书写步骤&#xff1a; 创建字节输出流对象写数据释放资源 演示:…

C语言开发单片机为什么大多数都采用全局变量的形式?

C语言开发单片机为什么大多数都采用全局变量的形式&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&…