• 详细了解Redis的八种数据类型及应用场景分析


    介绍

    Redis一共有几种数据类型?(注意是数据类型不是数据结构

    一共是八种,String、Hash、Set、List、Zset、Hyperloglog、Geo、Streams。

    1、为什么要把数据放在内存中?

    1. 内存的速度更快,10W QPS
    2. 减少计算的时间,减轻数据库压力

    2、如果是用内存的数据结构作为缓存,为什么不用HashMap或者Memcached?

    1. 更丰富的数据类型
    2. 支持多种编程语言
    3. 功能丰富:持久化机制、内存淘汰策略、事务、发布订阅、pipeline、LUA
    4. 支持集群、分布式

    3、Memcached和redis的区别是什么?

    Memcached只能存储KV、没有持久化机制、不支持主从复制、是多线程的。

    Reids默认有16个库(0-15),可以在配置文件redis.conf中修改。

    databases 16

    Redis Key的最大长度限制是512M,值得限制不同,有的是用长度限制的,有的是用个数限制的。

    String

    介绍

    最基本也是最常用的数据类型就是String。get和set命令就是String的操作命令,Redis的字符串被叫做二进制安全的字符串(Binary-safe strings)。

    String可以存储三种类型,INT(整数)、float(单精度浮点数)、string(字符串)。

    操作命令

    下面是它的一些操作命令

    1. # 存值(如果对同一个key set多次会直接覆盖旧值)
    2. set jack 2673
    3. # 取值
    4. get jack
    5. # 查看所有键
    6. keys *
    7. # 获取键总数(生产环境数据量大,慎用)
    8. dbsize
    9. # 查看键是否存在
    10. exists jack
    11. # 删除键
    12. del jack tonny
    13. # 重命名键
    14. rename jack tonny
    15. # 查看类型
    16. type jack
    17. # 获取指定范围的字符
    18. getrange jack 0 1
    19. # 获取值长度
    20. strlen jack
    21. # 字符串追加内容
    22. append jack good
    23. # 设置多个值(批量操作,原子性)
    24. mset jack 2673 tonny 2674
    25. # 获取多个值
    26. mget jack tonny
    27. # 设置值,如果key存在,则不成功
    28. setnx jack shuaige
    29. # 基于此实现分布式锁
    30. set key value [expiration EX seconds|PX milliseconds][NX|XX]
    31. # (整数)值递增(值不存在会得到1)
    32. incr jack
    33. incrby jack 100
    34. # (整数)值递减
    35. decr jack
    36. decrby jack 100
    37. # 浮点数增量
    38. set mf 2.6
    39. incrbyfloat mf 7.3

    应用场景

    1、缓存

    String类型,这是最常用的,可以缓存一些热点数据,比如首页新闻,可以显著提升热点数据的访问速度,同时减轻DB压力。

    2、分布式数据共享

    String 类型,因为Redis是分布式的独立服务,可以在多个应用之间共享。

    例如:分布式Session

    1. <dependency>
    2. <groupId>org.springframework.session</groupId>
    3. <artifactId>spring-session-data-redis</artifactId>
    4. </dependency>

    3、分布式锁

    参考《介绍几种常见的分布式锁写法》

    4、全局ID

    INT类型,INCRBY,利用原子性

    incrby userid 1000

    (分表分库的场景,一次性拿一段)

    5、计数器

    INT类型,INCR方法

    例如:文章的阅读量,微博点赞数,允许一定的延迟,先写入Redis再定时同步到数据库

    6、限流

    INT类型,INCR方法

    以访问者的IP和其他信息作为key,访问一次增加一次计数,超过次数则返回false。

    Hash

    介绍

    现在有一张teacher表

    假设我们还是通过String类型存储的话,存储的时候就要把 Teacher 实体类进行序列化,然后作为 value 只存储进去;修改的时候,也需要把 value 先取出来进行反序列化,比如把年龄更改为21岁,然后再序列化,再存进去,十分的繁琐,增加开销。

    我们需要单独获取、修改一个值,这时我们可以通过key分层的方式来实现,如下表:

    但是这样 key 会很多,key 也很长,占用空间,有没有更好的办法,这时候就用到我们的 Hash 类型,如下两张表所示:

    这样也便于集中管理,划分的粒度不同,可以按照实际场景,key 的过期时间,灵活度考虑选取哪一种存储方式。

    Hash用来存储多个无序的键值对,最大存储数量2^32-1(40亿左右)。

    优点:

    1. 把所有相关的值聚集到一个Key中,节省内存空间
    2. 只使用一个Key,减少Key冲突
    3. 当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU的消耗

    缺点:

    1. Field不能单独设置过期时间
    2. 需要考虑数据量分布的问题(field非常多的时候,无法分布到多个节点)

    操作命令

    1. # 设置、批量设置值
    2. hset h1 f 6
    3. hset h1 e 5
    4. hmset h1 a 1 b 2 c 3 d 4
    5. # 取值
    6. hget h1 a
    7. # 批量取值
    8. hmget h1 a b c d
    9. # 获取所有field
    10. hkeys h1
    11. # 获取所有field的值
    12. hvals h1
    13. # 返回哈希表中,所有的字段和值
    14. hgetall h1
    15. # 删除field
    16. hdel h1 a
    17. # 获取哈希表中字段的数量
    18. hlen h1

    应用场景

    String可以做的事情,Hash都可以做。再补充一个场景,购物车:

    key:用户id;field:商品id;value:商品数量;

    +1:hincr;

    -1:hdecr;

    删除:hincrby key field -1;

    全选:hgetall;

    商品数:hlen;

    List

    介绍

    存储有序的字符串(从左到右),元素可以重复,最大存储数量2^32-1(40亿左右)。

    下面通过画图来演示一下入队列,出队列

    操作命令

    1. # 左推
    2. lpush queue a
    3. lpush queue b c
    4. # 右推
    5. rpush queue d e
    6. # 左边移除并返回列表的第一个元素
    7. lpop queue
    8. # 右边移除并返回列表的第一个元素
    9. rpop queue
    10. # 通过索引获取列表中的元素
    11. lindex queue 0
    12. # 返回列表中指定区间内的元素
    13. lrange queue 0 -1

    应用场景

    1、列表

    例如用户的消息列表、网站的公告列表、活动列表、博客的文章列表、评论列表等,通过 LRANGE 取出一页,按顺序显示。

    2、队列/栈

    List还可以当做分布式环境的队列/栈使用。

    队列:先进先出,rpush 和 blpop

    栈:先进后出,rpush 和 brpop

    这里介绍两个阻塞的弹出操作:blpop/brpop,可以设置超时时间(单位:秒)。

    1. blpop:blpop key1 timeout,移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
    2. brpop:brpop key1 timeout,移出并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

    Set

    介绍

    Set 存储 String 类型的无序集合,最大存储数量 2^32-1(40亿左右)。

    如下图所示:

    操作命令

    1. # 添加一个或多个元素
    2. sadd myset a b c d e f g
    3. # 获取所有元素
    4. smembers myset
    5. # 统计元素个数
    6. scard myset
    7. # 随机获取一个元素
    8. srandmember myset
    9. # 随机弹出一个元素
    10. spop myset
    11. # 移除一个或者多个元素
    12. srem myset d e f
    13. # 查看元素是否存在
    14. sismember myset a
    15. # 获取差集
    16. sdiff set1 set2
    17. # 获取交集
    18. sinter set1 set2
    19. # 获取并集
    20. sunion set1 set2

    应用场景

    1、抽奖

    随机获取元素:spop myset

    2、点赞、签到、打卡

    我们以微博举例子,假设这条微博的ID是t1001,用户ID是u6001,

    用dianzan:t1001来维护t1001这条微博的所有点赞用户。

    点赞了这条微博:sadd dianzan:t1001 u6001

    取消点赞:srem dianzan:t1001 u6001

    是否点赞:sismember dianzan:t1001 u6001

    点赞的所有用户:smembers dianzan:t1001

    点赞数:scard dianzan:t1001

    比关系型数据库简单了许多。

    3、商品标签

    用 tags:i8001 来维护商品所有的标签。

    sadd tags:i8001 画面清晰细腻

    sadd tags:i8001 真彩清晰显示屏

    sadd tags:i8001 流畅至极

    4、商品筛选

    华为P40上线了,支持民族品牌,加到各个标签中去。

    sadd brand:huawei p40

    sadd os:android p40

    sadd screensize:6.0-6.24 p40

    买的时候筛选,牌子是华为,操作系统是安卓,屏幕大小在6.0-6.24之间的,取交集:

    sinter brand:huawei os:android screensize:6.0-6.24

    ZSet

    介绍

    sorted set 存储有序的元素。每个元素都有个 score,按照 score 从小到大排序。score 相同时,按照 key 的ASCII码排序。

    操作命令

    1. # 添加元素
    2. zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python
    3. # 获取全部元素
    4. zrange myset 0 -1 withscores
    5. zrevrange myzset 0 -1 withscores
    6. # 根据分数区间获取元素
    7. zrangebyscore myzset 20 30
    8. # 移除元素(也可以根据score rank删除)
    9. zrem myzset php cpp
    10. # 统计元素个数
    11. zcard myzset
    12. # 分值增加
    13. zincrby myzset 5 python
    14. # 根据分值min和max统计个数
    15. zcount myzset 20 60
    16. # 获取python排名
    17. zrank myzset python
    18. # 获取元素分数
    19. zscore myzset python

    应用场景

    排行榜

    今天是2021年5月23号,建一个 key 为 hotSearch:20210523 的 zset。

    放羊大叔这条新闻的id是n1234,每点击一下:zincrby hotSearch:20210523 1 n1234

    获取热搜排行榜前十条:zrevrange hotSearch:20210523 0 10 withscores

    袁老国士无双,一路走好,中华民族的儿女不会忘记您!

    BitMaps

    介绍

    BitMaps是在字符串类型上定义地位操作,一个字节由8个二进制位组成。如下图所示:

    m的ASCII码是109,对应的二进制数据是0110 1101

    操作命令

    1. # 设置字符串key为k1,value为mic
    2. set k1 mic
    3. # 取k1的第七位,结果是0
    4. getbit k1 6
    5. # 取k1的第八位为0,此时的ASCII码是108,对应字母是l
    6. setbit k1 7 0
    7. # 所以取出来值为lic
    8. get k1
    9. # 统计二进制中1的个数,一共是12个
    10. bitcount k1
    11. # 获取第一个1或者0的位置
    12. bitpos k1 1
    13. bitpos k1 1

    应用场景

    • BITOP AND destkey key [key ...],对一个或多个key求逻辑并,并将结果保存到 destkey。
    • BITOP OR destkey key [key ...],对一个或多个key求逻辑或,并将结果保存到 destkey。
    • BITOP XOR destkey key [key ...],对一个或多个key求逻辑异或,并将结果保存到 destkey。
    • BITOP NOT destkey key,对给定key求逻辑非,并将结果保存到 destkey。

    连续七天在线用户

    1. setbit firstday 0 1 //设置第一天uid是0的用户登录
    2. setbit firstday 1 0 //设置第一天uid是1的用户未登录
    3. setbit firstday 2 1 //设置第一天uid是2的用户登录
    4. ...
    5. setbit secondday 0 0 //设置第二天uid是0的用户未登录
    6. setbit secondday 1 1 //设置第二天uid是1的用户登录
    7. setbit secondday 2 1 //设置第二天uid是2的用户登录
    8. ... //以此类推

    那么在算连续七天在线用户就是:

    BITOP AND 7_both_online_users firstday secondday thirdday fourthday fifthday sixthday seventhday

    还可以应用访问统计、在线用户统计等等。

    Hyperloglog

    Hyperloglog 提供了一种不太精确的基数统计方法,用来统计一个集合中不重复的元素个数,比如统计网站的UV,或者应用的日活、月活,存在一定的误差。

    在 Redis 中实现的 Hyperloglog,只需要12k内存就能统计2^64个数据。

    1. public static void main(String[] args) {
    2. Jedis jedis = new Jedis("39.103.144.86", 6379);
    3. float size = 100000;
    4. for (int i = 0; i < size; i++) {
    5. jedis.pfadd("hll", "hll-" + i);
    6. }
    7. long total = jedis.pfcount("hll");
    8. System.out.println(String.format("统计个数: %s", total));
    9. System.out.println(String.format("正确率: %s", (total / size)));
    10. System.out.println(String.format("误差率: %s", 1 - (total / size)));
    11. jedis.close();
    12. }

    源码在:
    com/xhj/jedis/HyperLogLogTest.java

    Geo

    现在有这样一个需求,获取半径1公里以内的门店,那么我们就要把门店的经纬度存起来,如果存在数据库的话,一个字段存经度,一个字段存维度,计算距离比较复杂。现在我们通过 Redis 的 Geo 存储十分方便。

    1. # 存经纬度
    2. geoadd location 121.445 31.213 shanghai
    3. # 取经纬度
    4. geopos location shanghai

    可以增加地址位置信息、获取地址位置信息、计算两个位置的距离、获取指定范围内的地理位置集合等等。源码在:
    com/xhj/jedis/GeoTest.java

    1.    public static void main(String[] args) {
    2.        Jedis jedis = new Jedis("39.103.144.86", 6379);
    3.        Map<String, GeoCoordinate> geoMap = new HashMap<>();
    4.        GeoCoordinate coordinate = new GeoCoordinate(121.445, 31.213);
    5.        geoMap.put("shanghai", coordinate);
    6.        jedis.geoadd("positions", geoMap);
    7.        System.out.println(jedis.geopos("positions", "shanghai"));
    8.        jedis.close();
    9.   }

    Streams

    5.0推出的数据类型。支持多播的可持久化的消息队列,用于实现发布订阅功能,借鉴了Kafka的设计。

    应用场景总结

    • 缓存—提升热点数据的访问速度
    • 共享数据—数据的存储和共享的问题
    • 全局ID—分布式全局ID的生成方案(分库分表)
    • 分布式锁—进程间共享数据的原子操作保证
    • 在线用户统计和计数
    • 队列、栈—跨进程的队列/栈
    • 消息队列—异步解耦的消息机制
    • 服务注册与发现—RPC通信机制的服务协调中心(Dubbo支持Redis)
    • 购物车
    • 新浪用户消息时间线
    • 抽奖逻辑(礼物、转发)
    • 点赞、签到、打卡
    • 商品标签
    • 用户(商品)关注(推荐)模型
    • 电商产品筛选
    • 排行榜

    转载于:
    https://juejin.cn/post/6965870956103335949#heading-0

  • 相关阅读:
    MongoDB ubuntu 上安装 MongoDB7.0 附带配置文件说明
    站在AI大模型十字路口:实地探访2023服贸会
    MySQL之查询性能优化(十三)
    【Web】Ctfshow SSTI刷题记录1
    用 Rust 的 declarative macro 做了个小东西
    通过xsd校验xml
    JavaScript常见面试题(二)
    f-VAEGAN-D2:VAE+GAN处理零样本学习问题
    基于SpringMVC实现常见功能
    【特纳斯电子】基于物联网的空气质量检测-仿真设计
  • 原文地址:https://blog.csdn.net/wdj_yyds/article/details/125435008