在Web和移动应用的业务场景中,我们经常需要保存这样一种信息:一个key对应了一个数据集合。

  • 手机App中的每天的用户登录信息:一天对应一系列用户ID或移动设备ID;
  • 电商网站上商品的用户评论列表:一个商品对应了一系列的评论;
  • 用户在手机App上的签到打卡信息:一天对应一系列用户的签到记录;
  • 应用网站上的网页访问信息:一个网页对应一系列的访问点击。

Redis集合类型的特点就是一个键对应一系列的数据,所以非常适合用来存取这些数据。在这些场景中,除了记录信息,还需要对集合中的数据进行统计。

  • 在移动应用中,需要统计每天的新增用户数和第二天的留存用户数;
  • 在电商网站的商品评论中,需要统计评论列表中的最新评论;
  • 在签到打卡中,需要统计一个月内连续打卡的用户数;
  • 在网页访问记录中,需要统计独立访客量。

通常情况下,面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。所以,必须要选择能够非常高效地统计大量数据的集合类型。

要选择合适的集合,就得了解常用的集合统计模式。常见的四种统计模式,包括聚合统计、排序统计、二值状态统计和技术统计。

聚合统计

所谓聚合统计,就是指统计多个集合元素的聚合结果,包括:统计多个集合的共有元素(交集统计);把两个集合相比,统计其中一个集合独有的元素(差集统计);统计多个集合的所有元素(并集统计)。

统计手机App每天新增用户数和第二天的留存用户数,正好对应了聚合统计。

要完成这个统计任内务,可以用集合记录所有登录App的用户ID,同时,用另一个集合记录每天登录过App的用户ID,然后,再对这两个集合做聚合统计。

记录所有登录过App的用户ID,可以直接使用Set类型,把key设置为user:id,标识记录的是用户ID,value就是一个Set集合,里面是所有登录过App的用户ID,可以把这个Set叫做累计用户Set。

user:id 100301
100503
100218
...
100393

还需要把每天登录的用户ID记录到一个新集合中,key是user:id:{当天日期},例如user:id:20200803,value是Set集合,记录当天登录的用户ID,我们把这个集合叫做每日用户Set。

user:id:20200803 100301
100615
100398
...
100393

在统计每天的新增用户时,我们只用计算每日用户Set和累计用户Set的差集就行。

假如手机App在2020年8月3日上线,那么8月3日前是没有用户的,此时,累计用户Set是空集,当天登录的用户ID会被记录到key为user:id:20200803的Set中,所以,user:id:20200803这个Set中的用户就是当天的新增用户。

然后,计算user:id和user:id:20200803的并集结果,结果保存在user:id这个累计用户Set中。

# 由于采用集群模式,需保证数据在同一个哈希槽中才可以进行聚合计算,所以将相同的键前缀加上{}
192.168.125.128:7001>sadd {user:id}:20200803 100301 100393 100398 100615
# s union store
192.168.125.128:7001>sunionstore {user:id} {user:id} {user:id}:20200803

此时,user:id这个累计用户Set中就有了8月3日用户ID,等到8月4日在统计时,我们把8月4日登录的用户ID记录到user:id:20200804的Set中,接下来,我们执行sdiffstore命令计算user:id和user:id:20200804的差集,结果保存在user:new的Set中。

192.168.125.128:7001>sadd {user:id}:20200804 100301 100393 100398 100392
# 此处{user:id}:20200804必须放在前面,并且差集中只取{user:id}:20200804中独有的
192.168.125.128:7001>sdiffstore {user:id}:new {user:id}:20200804 {user:id}

这个差集在user:id:20200804中存在,但是不在user:id中,所以,user:id:new就是8月4日新增的用户。

当要计算8月4日保留用户时,只需要计算user:id:20200803和user:id:20200804的交集,就可以得到同时在user:id:20200803和20200804中的用户ID了。也就是2020年8月3日和2020年8月4日都登录的用户。

192.168.125.128:7001>sinterstore {user:id}:inter {user:id}:20200803 {user:id}:202000804

当需要对多个集合进行聚合计算时,Set类型会使一个不错的选择,但是Set的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致Redis实例阻塞。所以可以从集群中选择一个从库,让它专门负责聚合计算,或者把数据读到客户端,在客户端来完成聚合统计,这样就可以规避阻塞主库实例和其他从库实例的风险了。

排序统计

电商网站上要提供最新评论列表,就这需要集合类型能对元素保序,也就是说,集合中的元素可以按序排列,这种元素保序的集合类型叫做有序集合。

在Redis中常见的4个集合类型中(List、Hash、Set、Sorted Set),List和Sort Set就属于有序集合。

List是按照元素进入List的顺序进行排序的,而Sorted Set可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。

如果使用List,每个商品对应一个List,这个List包含了对这个上皮内的所有评论,而且会按照评论时间保存这些评论,每来一个新评论,就用LPUSH命令把他插入List的队头。

在只有一页评论的时候,我们可以很清晰地看到最新的评论,但是,在实际应用给中,网站一般会分页显示最新的评论列表,一旦涉及到分页操作,List就可能会出现问题了。

假设当前的评论List是{A,B,C,D,E,F},其中A是最新的评论,F是最早的评论,在展示第一页的3个评论时,可以用以下命令。查出第一页为{A,B,C},第二页为{D,E,F}。

192.168.125.128:7001>lpush product1 F E D C B A
192.168.125.128:7001>lrange product1 0 2
192.168.125.128:7001>lrange product1 3 5

如果在展示第二页前,又产生一个新评论G,评论G就会被LPUSH命令插入到评论List的队头,评论List就会变成{G,A,B,C,D,E,F}。此时获取第二页,就会变成{C,D,E},评论C又被展示出来了。

和List相比,Sorted Set就不存在这个问题,因为它时根据元素的实际权重来排序和获取数据的。

可以按评论时间先后给每条评论设置一个权重值,然后再把评论保存到Sorted Set中。Sorted Set的ZRANGEBYSCORE命令就可以按权重排序后返回元素。这样的话,即使集合中的元素频繁更新,Sorted Set也能通过ZRANGEBYSCORE命令准确地获取按序排列的数据。

加入越新的评论权重却大,目前最新评论的权重时N,执行下面的命令,就可以获取最新的10条评论。

# zadd key socre member score member ...
192.168.125.128:7001>zadd comments 1 A 2 B 3 C 4 D 5 E 6 F 7 G 8 H 9 I 10 J
192.168.125.128:7001>zrangebyscore comments 1 10

所以,在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者分页显示,建议优先考虑Sorted Set。

二值状态统计

二值状态统计就是值集合元素的取值只有0和1两种。在签到打卡的场景中,我们只需要记录签到或未签到,所以它就是非常典型的二值状态。

在签到统计时,每个用户一天的签到用1个bit位就能表示,一个月签到的情况最多用31个bit位就可以,而一年的签到也只需要用365个bit位,根本不用太复杂的集合类型。这时可以选择Bitmap,这是Reids提供的扩展数据类型。

Bitmap本身是用String类型作为底层数据结构实现的一种统计二值状态的数据类型。String类型是会保存位二进制的字节数组,所以,Redis就把字节数组的每个bit位利用起来,用来表示一个元素的二值状态。可以把Bitmap看作是一个bit数组。

Bitmap提供了GETBIT/SETBIT操作,使用一个偏移值offset对bit数组的某个bit位进行读写。不过,需要注意的是,Bitmap的偏移量是从0开始计算的,也就是说offset的最小值是0。当使用SETBIT对一个bit位进行写操作时,这个bit位会被设置为1。Bitmap还提供了BITCOUNT操作,用来统计这个bit数组中所有1的个数。

假如要统计ID 3000的用户在2020年8月份的签到情况,就可以按下面的步骤进行操作。

# setbit key offset value
# offset 2 标识8月3日,8月1日用offset 0标识
# 第一步,记录8月3日,3000已签到
192.168.125.128:7001>setbit uid:sign:3000:202008 2 1# 第二步,检查该用户8月3日是否签到
192.168.125.128:7001>getbit uid:sign:3000:202008 2# 第三步,统计该用户8月份的签到次数
192.168.125.128:7001>bitcount uid:sign:3000:202008

如果记录了1亿个用户10天的签到情况,那么10天都连续签到的用户数如何统计?

Bitmap支持用BITOP命令对多个Bitmap按位做“与”、“或”、“异或”的操作,操作的结果会保存到一个新的Bitmap中。

在统计1亿个用户连续10天签到的情况时,可以把每天的日期作为key,每个key对应一个1亿位的Bitmap,每一个bit位对应一个用户当天的签到情况,注意用户的顺序不能乱。

接下来,对10个Bitmap做“与”操作,得到的结果也是一个Bitmap。在这个Bitmap中,只有10天都签到的用户对应的bit位上的值才会是1。最后,可以用BITCOUNT统计下Bitmap中1的个数,这就是连续签到10天的用户总数了。

每天使用1个1亿位的Bitmap,大约占12MB内存(10^8/8/1024/1024),10天的Bitmap的内存开销约为120MB,内存压力不算大。但是在实际应用时,最好对Bitmap设置过期时间,让Redis自动删除不再需要的签到记录,以节省内存开销。

基数统计

基数统计就是指统计一个集合中不重复的元素个数。比如统计网页的UV。

网页UV的统计有个独特的地方,就是需要去重,一个用户一天内的多次访问只能算作依次。在Redis的集合类型中,Set类型默认支持去重,所以看到有去重需求时,我们可能第一时间想到Set类型。

有一个用户user1访问page1时,把这个信息添加到Set中。当user1再来访问时,Set的去重功能就保证了不会重复记录user1的访问次数,这样,user1就算一个独立访客,当你需要统计UV时,可以直接用SCARD命令,这个命令会返回一个集合中的元素个数。

但是page1非常火爆,UV达到了千万,这个时候,一个Set就要记录千万个用户ID,对于一个搞大促的电商网站而言,这样的页面可能有成千上万个,如果每个页面都用这样的一个Set,就会消耗很大的内存空间。

Redis提供了HyperLogLog,这是一种用于统计基数的数据集合类型,它的最大优势就在于,当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小。

在Redis中,每个HyperLogLog只需要花费12kb内存,就可以计算接近2^64个元素的基数。

在统计UV时,可以用PFADD把访问页面的每个用户都添加到HyperLogLog中。

# 重复值无法添加
192.168.125.128:7001>pfadd page1:uv user1 user2 user3 user4 user5
# 获取统计数据
192.168.125.128:7001>pfcount page1:uv

HyperLogLog的统计规则是基于概率完成的,所以它给出的统计结果有一定误差的,标准误算率是0.81%。这就意味着,使用HyperLogLog统计的UV是100万,但实际的UV可能是101万。虽然误差率不大,但是如果需要精确的统计结果的话,最好还是使用Set或Hash类型。

Redis数据类型的选择相关推荐

  1. Redis-cluster集群【第一篇】:redis安装及redis数据类型

    Redis介绍: 一.介绍 redis 是一个开源的.使用C语言编写的.支持网络交互的.可以基于内存也可以持久化的Key-Value数据库. redis的源码非常简单,只要有时间看看谭浩强的C语言,在 ...

  2. redis 数据类型详解 以及 redis适用场景场合

    redis 数据类型详解 以及 redis适用场景场合 1. MySql+Memcached架构的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访 ...

  3. 【redis】二、redis数据类型

    文章目录 数据存储类型介绍 业务数据的特殊性 作为缓存使用 附加功能 Redis 数据类型(5种常用) Redis 数据存储格式 string string类型数据的基本操作 单数据操作与多数据操作的 ...

  4. redis数据类型及常用数据操作

    redis数据类型及操作 1 官方文档(大全) 2 String-字符串 3 Hash-字典 4 List-列表 5 Set-无序集合 6 Sorted Set-有序集合 7 Pub/Sub-订阅/发 ...

  5. redis介绍, redis安装, redis持久化, redis数据类型

    redis介绍 Redis和Memcached类似,也属于k-v数据存储 Redis官网redis.io, 当前最新稳定版4.0.1 支持更多value类型,除了和string外,还支持hash.li ...

  6. 第3节-Redis数据类型介绍以及应用

    第3节-Redis数据类型介绍以及应用 1.9大类型 String(字符类型) Hash(散列类型) List(列表类型) Set(集合类型) SortedSort(有序集合类型,简称zset) Bi ...

  7. Java Web学习day26------Redis基础、Redis数据类型、常用指令、jedis、持久化

    Redis基础.Redis数据类型.常用指令.jedis.持久化 1. Redis 简介 1.1 NoSQL概念 1.2 Redis概念 1.3 Redis 的下载与安装 1.4 Redis服务器启动 ...

  8. Marco's Java【Redis入门(三) 之 Redis数据类型及用法】

    前言 在啃完上节的Redis的配置文件redis.conf这块 "硬石头" 之后,接下来,咱们继续啃Redis数据类型- Redis 相对于 Memcache.Tokyo Tyra ...

  9. Redis 数据类型之(底层解析)

    Redis 数据类型之(底层解析) Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redi ...

最新文章

  1. Spring3.1+Quertz1.8实现多个计划任务
  2. 回调函数中window.open()被拦截
  3. UVALive5461 UVA615 POJ1308 Is It A Tree?(解法二)【废除!!!】
  4. Vue.js 学习笔记 七 控制样式
  5. HttpUtility.UrlEncode、HttpUtility.UrlDecode、Server.UrlEncode、Server.UrlDecode的区分与应用
  6. mysqldump备份过程中都干了些什么
  7. Unity C#代码小技巧
  8. 黄色叹号_平行进口车有质量问题?许多新车都有的黄色感叹号故障灯是什么?...
  9. 报错 xxx@1.0.0 dev D:\ webpack-dev-server --inline --progress --configbuild/webpack.dev.conf.js
  10. 区位码,国标码,交换码,内码,外码
  11. 超级机器人大战A(GBA)帅气攻略(超级系流程1)
  12. 《烈烈先秦》6、六国的噩梦——大秦武安君白起
  13. ol+天地图+geoserver_天地图离线瓦片的打包与发布(GeoServer)
  14. 分布式系统架构的优缺点
  15. 塔尔萨大学计算机科学专业,塔尔萨大学有哪些专业_专业排名(USNEWS美国大学排名)...
  16. RETINA 屏幕1px 边框实现
  17. 6-1 调用函数打印闰年 (15 分)
  18. 计算机应用基础(专)【9】
  19. 网站日志流量分析系统之(日志收集)
  20. 面经:2020校招中兴提前批面试经历

热门文章

  1. 【181101】VC++电子地图绘制工具源代码
  2. java jodd框架介绍及使用示例
  3. 数字IC设计工程师职业发展规划是什么样的?
  4. 对比分析方法,数据异动的假设,如何找出羊毛党
  5. 本白痴的第一个博客(就把自己的图形界面加c语言的滴滴打车信息系统传一下吧)
  6. 阻塞、非阻塞,同步、异步
  7. Vysor 安装教程
  8. Android 百分比布局、权重、隐藏TitleBar、引入自定义控件
  9. 第十二讲 dom对象(DOM对象、document对象的常用方法、节点、查找结点、 查看/修改/删除属性节点、创建和增加节点)
  10. 接收Cookie总结