当前位置: 首页 > news >正文

Redis(高阶篇)05章——案例落地实战bitmap/hyperloglog/GEO

一、先看看大厂真实需求+面试题反馈

(1)面试题1

  1. 抖音电商直播,主播介绍的商品有评论,1个商品对应了一系列的评论,排序+展现+取前10条记录
  2. 用户在手机APP上的签到打卡信息:1天对应一系列用户的签到记录,新浪微博、钉钉打卡签到,来没来如何统计
  3. 应用网站上的网页访问信息:一个网页对应一系列的访问点击,淘宝网首页,每天有多少人浏览首页
  4. 公司系统上线后,说一下UV、PV、DAU分别是多少?

(2)面试题2

(3)需求痛点

  1. 亿级数据的收集+清洗+统计+展现
  2. 一句话:存的进+去得快+多维度展现
  3. 真正有价值的是统计

二、统计的类型有哪些

(1)聚合统计

  1. 统计多个集合元素的聚合结果,就是前面讲解过的交差并等集合统计
  2. 复习命令
  3. 交差并集和聚合函数的应用

(2)排序统计

  1. 抖音短视频最新评论留言的场景,请你设计一个展现列表。(考察数据结构和设计思路)
  2. 设计案例和回答思路:
  3. answer:
    1. zset
    2. 在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议使用ZSet

(3)二值统计

  1. 集合元素的取值就只有0和1两种。在钉钉上签到打卡的场景中,我们只用记录有签到(1)或没有签单(0)
  2. 见bitmap

(4)基数统计

  1. 指统计一个集合中不重复的元素个数
  2. 见hyperloglog

三、hyperloglog

(1)说名词,行话谈资

3.1.1什么是UV

  1. Unique Visitor,独立访客,一般理解为客户端IP
  2. 需要去重考虑

3.1.2什么是PV

  1. Page View,页面浏览量
  2. 不用去重

3.1.3什么是DAU

  1. Daily Active User,日活跃量用户,登录或者使用了某个产品的用户数(去重复登录的用户)
  2. 常用于反映网站、互联网应用或者网络游戏的运营情况

3.1.4什么是MAU

Monthly Active User,月活跃用户量

(2)看需求

(3)是什么(小白篇讲解过,快速复习一下)

3.3.1基数

  1. 是一种数据集,去重复后的真实个数
  2. 案例Case                                                                                                                                   

3.3.2取重复统计功能的基数估算算法-就是HyperLogLog

3.3.3基数统计

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

3.3.4一句话

一句话:脱水后的真实数据

3.3.5基本命令

(4)HyperLogLog如何做的?如何演化出来的?

3.4.1基数统计就算HyperLogLog

3.4.2去重复统计你先会想到哪些方式?

(1)HashSet
(2)bitmap
(3)结论
  1. 样本元素越多内存消耗急剧增大,难以管控+各种慢,对于亿级统计不太合适
  2. 量变引起质变
(4)办法

3.4.3原理说明

(1)只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容
(2)有误差
  1. HyperLogLog提供不精确的去重计数方案
  2. 只牺牲准确率来换取空间,误差仅仅只是0.81%左右
(3)这个误差率如何来的?
  1. Redis new data structure: the HyperLogLog - <antirez>
  2. Redis之父安特雷兹回答:

(5)淘宝网站首页亿级UV的Redis统计方案

3.5.1需求

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

3.5.2方案讨论

  1. 用MySQL,×
  2. 用redis的hash结构存储
  3. HyperLogLog

3.5.3HyperLogLogService

package com.atguigu.redis.service;

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;

@Service
@Slf4j
public class HyperLogLogService
{
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 模拟后台有用户点击首页,每个用户来自不同ip地址
     */
    @PostConstruct
    public void init()
    {
        log.info("------模拟后台有用户点击首页,每个用户来自不同ip地址");
        new Thread(() -> {
            String ip = null;
            for (int i = 1; i <=200; i++) {
                Random r = new Random();
                ip = r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256);

                Long hll = redisTemplate.opsForHyperLogLog().add("hll", ip);
                log.info("ip={},该ip地址访问首页的次数={}",ip,hll);
                //暂停几秒钟线程
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        },"t1").start();
    }

}

3.5.4HyperLogLogController

package com.atguigu.redis.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
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(description = "淘宝亿级UV的Redis统计方案")
@RestController
@Slf4j
public class HyperLogLogController
{
    @Resource
    private RedisTemplate redisTemplate;

    @ApiOperation("获得IP去重后的首页访问量")
    @RequestMapping(value = "/uv",method = RequestMethod.GET)
    public long uv()
    {
        //pfcount
        return redisTemplate.opsForHyperLogLog().size("hll");
    }

}

四、GEO

(1)Redis之GEO

4.1.1大厂面试题简介

4.1.2地理知识说明

4.1.3如何获得某个地址的经纬度

4.1.4命令复习第二次

  1. GEOADD 添加经纬度坐标
  2. GEOPOS返回经纬度
  3. GEOHASH返回坐标的geohash表示
  4. GEODIST两个位置之间的距离
  5. GEORADIUS,以半径为中心,查找附近的XXX
  6. GEORADIUSBYMEMBER

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

4.2.1需求分析

4.2.2架构设计

  1. Redis的新类型GEO
  2. 命令:http://www.redis.cn/commands/geoadd.html

4.2.3编码实现

  1. 关键点
  2. GeoController
    package com.atguigu.redis7.controller;
    
    import com.atguigu.redis7.service.GeoService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.data.geo.*;
    import org.springframework.data.redis.connection.RedisGeoCommands;
    import org.springframework.web.bind.annotation.GetMapping;
    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 java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Api(tags = "美团地图位置附近的酒店推送GEO")
    @RestController
    @Slf4j
    public class GeoController
    {
        @Resource
        private GeoService geoService;
    
        @ApiOperation("添加坐标geoadd")
        @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 radiusByMember()
        {
            return geoService.radiusByMember();
        }
    
    }
    
  3. GeoService
    package com.atguigu.redis7.service;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.geo.Distance;
    import org.springframework.data.geo.GeoResults;
    import org.springframework.data.geo.Metrics;
    import org.springframework.data.geo.Point;
    import org.springframework.data.geo.Circle;
    import org.springframework.data.redis.connection.RedisGeoCommands;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Service
    @Slf4j
    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.403963,39.915119));
            map.put("故宫",new Point(116.403414 ,39.924091));
            map.put("长城" ,new Point(116.024067,40.362639));
    
            redisTemplate.opsForGeo().add(CITY,map);
    
            return map.toString();
        }
    
        public Point position(String member) {
            //获取经纬度坐标
            List<Point> list= this.redisTemplate.opsForGeo().position(CITY,member);
            return list.get(0);
        }
    
    
        public String hash(String member) {
            //geohash算法生成的base32编码值
            List<String> list= this.redisTemplate.opsForGeo().hash(CITY,member);
            return list.get(0);
        }
    
    
        public Distance distance(String member1, String member2) {
            //获取两个给定位置之间的距离
            Distance distance= this.redisTemplate.opsForGeo().distance(CITY,member1,member2, RedisGeoCommands.DistanceUnit.KILOMETERS);
            return distance;
        }
    
        public GeoResults radiusByxy() {
            //通过经度,纬度查找附近的,北京王府井位置116.418017,39.914402
            Circle circle = new Circle(116.418017, 39.914402, Metrics.KILOMETERS.getMultiplier());
            //返回50条
            RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(50);
            GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults= this.redisTemplate.opsForGeo().radius(CITY,circle, args);
            return geoResults;
        }
    
        public GeoResults radiusByMember() {
            //通过地方查找附近
            String member="天安门";
            //返回50条
            RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(50);
            //半径10公里内
            Distance distance=new Distance(10, Metrics.KILOMETERS);
            GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults= this.redisTemplate.opsForGeo().radius(CITY,member, distance,args);
            return geoResults;
        }
    }

五、bitmap

(1)大厂真实面试题案例

  1. 日活统计
  2. 连续签到打卡
  3. 最近一周的活跃用户
  4. 统计指定用户一年之中的登录天数
  5. 某用户按照一年365天,哪几天登陆过,哪几天没有登录?

(2)是什么

  1. 图示:
  2. 一句话:由0和1状态表现的二进制位的bit数组

(3)能干嘛

5.3.1用于状态统计

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

5.3.2看需求

  1. 用户是否登录过Y、N,比如京东每日签到送京豆
  2. 电影、广告是否被点击播放过
  3. 钉钉打卡上下班,签到统计

(4)京东签到领取京豆

5.4.1需求说明

                     

5.4.2小厂方法,传统mysql方式

  1. 建表SQL
  2. 困难和解决思路

5.4.3大厂方法,基于Redis的Bitmap实现签到日历

(5)命令复习,第二次

  1. setbit:
    1. setbit key offset value
    2. set bit 键 偏移位 只能0或1
    3. Bitmap的偏移量是从0开始算的
  2. getbit:get bit key offset
  3. setbit和getbit案例说明:
    1. 按照天:                                                                                                                           
    2. 按照年:
  4. bitmap的底层编码说明,get命令操作如何:
    1. 实质是二进制的ascii编码对应
    2. redis里用type命令看看bitmap实质是什么类型
    3. man ascii
    4. 设置命令
  5. strlen:统计字节数占用多少
  6. bitcount:
    1. 全部键里面含1的有多少个?
    2. 一年365天,全年天天登录占用多少字节?
  7. bitop:

相关文章:

  • 在 Windows 环境下部署 WebIssues:完整指南
  • leetcode21.合并两个有序链表
  • Python常见面试题的详解15
  • stm32hal库寻迹+蓝牙智能车(STM32F103C8T6)
  • SOME/IP--协议英文原文讲解10
  • 阿里云如何协助解决操作系统兼容性问题
  • 【小游戏】C++控制台版本俄罗斯轮盘赌
  • 四、数据湖应用平台架构
  • 2025年-G11-Lc85-110.平衡二叉树-java版
  • NLP-RNN-LSTM浅析
  • XTOM-TRANSFORM自动化三维测量系统用于汽车零部件质量控制
  • three.js之特殊材质效果
  • linux+KMS+AD域自动激活
  • docker安装ros2 并在windows中显示docker内ubuntu系统窗口并且vscode编程
  • 获取每月最后一个工作日:考虑法定节假日与调休
  • IDEA中查询Maven项目的依赖树
  • 鸿蒙初学者学习手册(HarmonyOSNext_API14)_自定义动画API(@ohos.animator (动画) )
  • RabbitMQ的脑裂(网络分区)问题
  • 推荐一款AI大模型托管平台-OpenWebUI
  • Jenkins 部署在 Mac 并在局域网内通过 ip 访问
  • 企业做响应式网站好吗/网站seo推广计划
  • 想自己做网站该学些什么/怎么推广一个app
  • 江苏省建设信息网/济南seo小黑seo
  • 怎样将自己做的网站发布到外网上/宁波网站建设公司
  • 免费项目管理软件app/太原seo全网营销
  • 在线做高中试卷的网站/线上卖货平台有哪些