Redis支持五种数据类型:String(字符串) / hash(哈希) / list(列表)/set(集合)/zset(有序集合)以及几种高级数据类型 BitMaps / HyperLogLog 这两种


key:

因为Rediskey-value进行存储的数据库,我们首先来看下key能有几种类型:
Redis key的类型对应value的类型,我们可以从以下几个方面来看:

  1. key的类型
  2. key的命名规范
  3. key的过期时间

    key的类型

    因为key的数据类型对应value的数据类型,所以共有五种String/list/set/zset/hash ```powershell

    字符串

    这里我们使用值插入

    127.0.0.1:6379> set weather “yby” OK 127.0.0.1:6379> TYpe weather string

这里是对于 list

我们使用 LPUSH

127.0.0.1:6379> LPUSH name “aaa” (integer) 1 127.0.0.1:6379> TYPE name list

对于集合则使用 SADD

127.0.0.1:6379> SADD pat “aaa” (integer) 1 127.0.0.1:6379> TYPE pat set

对于 hash 需要使用 HMSET

其中包含了多个键值对

127.0.0.1:6379> HMSET userid:1 username xiaoming password 123456 OK 127.0.0.1:6379> HGETALL userid:1 1) “username” 2) “xiaoming” 3) “password” 4) “123456”

对于有序集合,其元素存在唯一性

127.0.0.1:6379> zadd yby 1 nihao (integer) 1 127.0.0.1:6379> Type yby zset

  1. <a name="WNRDQ"></a>
  2. ### key的命名规范
  3. (1)命名不能过长,要不会影响查找效率,并且占据内存空间<br />(2)命名不能过短,否则会使得 `key` 的可读性变差
  4. <a name="lzdq6"></a>
  5. ### Key的过期时间
  6. `Redis`允许为 `key`设置一个过期时间,也就是"到点自动删除"。(1)可以避免面使用频率不高的 `key`长期存在;(2)控制缓存的失效时间
  7. <a name="uAtX8"></a>
  8. ### 一些 key 的常用命令
  9. ```powershell
  10. #设置过期时间
  11. 127.0.0.1:6379> expire "yby" 1
  12. (integer) 1
  13. 127.0.0.1:6379> get "yby"
  14. (nil)
  15. #查找指定模式的键
  16. 127.0.0.1:6379> keys name*
  17. 1) "name"
  18. #TTL
  19. #检查 key剩余的过期时间
  20. # 永久有效返回 -1 / 当过期或者被删除,返回 -2
  21. 127.0.0.1:6379> keys name*
  22. 1) "name"
  23. 127.0.0.1:6379> ttl name
  24. (integer) -1

数据类型

String字符串

一个字符串最多能存储 512MB 的内容

  1. 127.0.0.1:6379> set website "www.yby.com"
  2. OK
  3. 127.0.0.1:6379> get website
  4. "www.yby.com"

Redis的底层并没有使用 C 的字符类型,而是自定义特殊结构 SDS

  1. (1)
  2. struct sdshdr{
  3. // 记录 buf 数组中已使用字符的数量,等于 SDS 保存字符串的长度
  4. int len;
  5. // 记录 buf 数组中为使用的字符数量
  6. int free;
  7. // 字符数组,用于保存字符串
  8. char buf[];
  9. }

1331556492-0.gif
结尾为 “\0” 结尾

并且分配了冗余空间:

1331556143-1.gif

自动扩容:

当所占空间小 1 MB,扩容是成倍方式增加;
当空间超过 1MB时,每次只增加 1MB


bitmap(位图)

比如我们在项目中,记录日活跃用户时,会需要存储一些 boolean类型数据。为了解决这个问题,redis提供了位图。bitmap同样属于String类型,每次能存储,8 个位 = 1个字节,1024个字节 = 1 KB,1024 KB = 1MB,即一个MB可以存储 8388608 条记录,属于String类型,由多个字节组成。

  1. public void recordDAU(int userId) {
  2. String redisKey = RedisKeyUtil.getDauKey(df.format(new Date()));
  3. redisTemplate.opsForValue().setBit(redisKey, userId, true);
  4. }

1333395108-0.gif
位图结构使用位来实现储存信息,大大降低了内存空间使用
我们来看下位图的常用命令:

  1. SETBIT key offset value
  2. # 如上个例子
  3. key = dau:2020:3:10
  4. # 用户访问就将已用户 id 为小标的位置 1 即可
  5. userId = 5118133

命令描述

  • 针对 key 存储的字符串值,设置或清除指定偏移量 offset 上的位 (bit)
  • 位的设置或清除取决于 value 值,即1或0
  • 当key不存在时,会创建一个新的字符串。而且这个字符串的长度会伸展,直到可以满足指定的偏移量 offset(0 ≤offset< 2^32),在伸展过程中,新增的位的值被设置为0
  • key在原始状态下,所有的位都为 0

在真的做业务的时候,一般不使用uuid作为offset,这样消耗的内存太大了。


hash哈希散列

hash散列是由字符串类型的fieldvalue组成的映射表,可以理解为一个包含了多个键值对的集合

  1. 127.0.0.1:6379> hmset user:1 id:1 name:cao
  2. OK
  3. 127.0.0.1:6379> hgetall user:1
  4. 1) "id:1"
  5. 2) "name:cao"

hash存储结构如下图所示
142P321D-0.gif
hash的底部数据结构

  1. 数据量较少时(字符串总长度 < 64,键值对 < 512) 使用ziplist
  2. 其他情况,使用dict(HashTable)

list列表

相当于linkedList,是一个链表而非数组,插入删除为O(1),查询为O(n)

  1. 127.0.0.1:6379> LPUSH biancheng Java
  2. (integer) 1
  3. 127.0.0.1:6379> LPUSH biancheng Python
  4. (integer) 2
  5. 127.0.0.1:6379> LRANGE biancheng 0 -1
  6. 1) "Python"
  7. 2) "Java"

底层数据结构:

  1. quicklist进行存储
  2. quicklist是由双向链表和ziplist共同组成

    ziplist:是 Redis 为节省内存而开发的,它是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表了可以包含任意多个节点,每个节点都可以保存一个字符数组或者整数值。

  1. 127.0.0.1:6379> Lpush yby gf
  2. (integer) 1
  3. 127.0.0.1:6379> Lrange yby 0 -1
  4. 1) "gf"
  5. #从右侧插入
  6. 127.0.0.1:6379> Rpush yby lv
  7. (integer) 2
  8. 127.0.0.1:6379> Rpush yby haGF
  9. (integer) 3
  10. 127.0.0.1:6379> lrange yby 0 -1
  11. 1) "gf"
  12. 2) "lv"
  13. 3) "haGF"
  14. # 在 haGF 前面插入 dd
  15. 127.0.0.1:6379> linsert yby before haGF dd
  16. (integer) 4
  17. 127.0.0.1:6379> lrange yby 0 -1
  18. 1) "gf"
  19. 2) "lv"
  20. 3) "dd"
  21. 4) "haGF"
  22. # 从左侧弹出
  23. 127.0.0.1:6379> Lpop yby
  24. "gf"
  25. # 从右侧弹出
  26. 127.0.0.1:6379> rpop yby
  27. "haGF"

可以作为异步队列使用:将任务对应的字符串存入redis,然后进行轮询,从其中读取任务


set集合(可自动去重)

  1. 127.0.0.1:6379> SADD www.biancheng HTML
  2. (integer) 1
  3. 127.0.0.1:6379> SADD www.biancheng Pandas
  4. (integer) 1
  5. 127.0.0.1:6379> SMEMBERS www.biancheng
  6. 1) "HTML"
  7. 2) "Pandas"

数据结构:
两种相结合的底层存储方式intsethash table,当结合内保存的 ( 所有成员都是整数值 / 保存的成员数量不超过 512 个),此时使用intset。否则使用hash table
intset的实际结构,content是一个数组
14343055O-0.gif


zset有序集合

set的区别:

  1. 是有序排列的
  2. 成员都是字符串类型
  3. 每个成员都关联了一个 double类型的score,从而实现了对于成员的排序

底层数据结构:

  1. ziplist 压缩列表(list)中的

(1)成员数量 < 128 个
(2)每个成员的字符串长度小于 64 字节
143P41922-0.gif

  • zlbyte是标识当前占用的总字节数
  • zltail标识尾部元素相对于起始元素的偏移量
  • zllen是指ziplistentry的数量
  • entry用来存放具体的数据项
  • zllend标识内存结束点

143P45294-1.gif

  1. skiplist:也就是跳表

其时间复杂度为O(nlogn),比如删除、插入、查找的时间复杂度
143P44491-2.gif

  1. # 在有序列表中添加 score / element
  2. > zadd song 1 heyJude 2 seeyouagain
  3. (integer) 2
  4. #获取列表中的元素
  5. > zrange song 0 -1
  6. 1) "heyJude"
  7. 2) "seeyouagain"
  8. # 从一个相反的顺序返回
  9. > zrevrange song 0 -1
  10. 1) "seeyouagain"
  11. 2) "heyJude"
  12. # 查看某个元素的评分
  13. > zscore song heyJude
  14. 1.0

HyperLogLog

HyperLogLog实际上是一个计数器,我们的项目中有用到

  1. // 这里虽然是使用 Java 语言,但是思路是一样
  2. // 获取独立访客
  3. public void record(String ip){
  4. // 这里获得 以日期为 key 的 RedisKey
  5. String redisKey = RedisUtil.getUvKey(df.format(new Date()));
  6. // 传入 ip
  7. redisTemplate.opsForHyperLogLog().add(redisKey, ip);
  8. }
  9. public long calculateUV(Date start, Date end){
  10. // 首先使用一个 List 来保存时间段内的 key
  11. List<String> keyList = new ArrayList<>();
  12. Calendar calendar = Calendar.getInstance();
  13. calendar.setTime(start);
  14. while(!calendar.getTime().after(end)){
  15. String key = RedisUtil.getUvKey(df.format(calendar.getTime()));
  16. keyList.add(key);
  17. calendar.add(Calendar.DATE, 1);
  18. }
  19. // 合并数据
  20. String redisKey = RedisKeyUtil.getUvKey(df.format(start),df.format(end));
  21. redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());
  22. // 返回统计结果
  23. return redisTemplate.opsForHyperLogLog().size(redisKey);
  24. }

redis客户端中相当于下面的操作

  1. # 添加元素
  2. Pfadd day:2020-5-11 yby
  3. (integer) true
  4. > Pfadd day:2020-5-11 ddd
  5. (integer) true
  6. > Pfadd day:2020-5-11 sx
  7. (integer) true
  8. > Pfadd day:2020-5-12 sx
  9. (integer) true
  10. > Pfadd day:2020-5-12 PDDD
  11. (integer) true
  12. > Pfcount day:2020-5-11
  13. (integer) 3
  14. # 合并两个 key
  15. > pfmerge day:2020-5-11 day:2020-5-12
  16. (integer) 1
  17. # 计算二者的个数(去重)
  18. > pfcount day:2020-5-11
  19. (integer) 4

HyperLogLog适用于计算基数,但是并不存储数据的内容,主要用于大数计算。
应用场景:

  1. bitmap配合使用,bitmap标识活跃,**hyperLogLog**用于计数
  2. 所占用的内存较少,一个**hyperloglog****key**一般占用 12k 内存