Redis支持五种数据类型:String(字符串) / hash(哈希) / list(列表)/set(集合)/zset(有序集合)以及几种高级数据类型 BitMaps / HyperLogLog 这两种
key:
因为Redis是 key-value进行存储的数据库,我们首先来看下key能有几种类型:
Redis 中 key的类型对应value的类型,我们可以从以下几个方面来看:
key的类型key的命名规范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
<a name="WNRDQ"></a>### key的命名规范(1)命名不能过长,要不会影响查找效率,并且占据内存空间<br />(2)命名不能过短,否则会使得 `key` 的可读性变差<a name="lzdq6"></a>### Key的过期时间`Redis`允许为 `key`设置一个过期时间,也就是"到点自动删除"。(1)可以避免面使用频率不高的 `key`长期存在;(2)控制缓存的失效时间<a name="uAtX8"></a>### 一些 key 的常用命令```powershell#设置过期时间127.0.0.1:6379> expire "yby" 1(integer) 1127.0.0.1:6379> get "yby"(nil)#查找指定模式的键127.0.0.1:6379> keys name*1) "name"#TTL#检查 key剩余的过期时间# 永久有效返回 -1 / 当过期或者被删除,返回 -2127.0.0.1:6379> keys name*1) "name"127.0.0.1:6379> ttl name(integer) -1
数据类型
String字符串
一个字符串最多能存储 512MB 的内容
127.0.0.1:6379> set website "www.yby.com"OK127.0.0.1:6379> get website"www.yby.com"
Redis的底层并没有使用 C 的字符类型,而是自定义特殊结构 SDS
(1)struct sdshdr{// 记录 buf 数组中已使用字符的数量,等于 SDS 保存字符串的长度int len;// 记录 buf 数组中为使用的字符数量int free;// 字符数组,用于保存字符串char buf[];}
并且分配了冗余空间:
自动扩容:
当所占空间小 1 MB,扩容是成倍方式增加;
当空间超过 1MB时,每次只增加 1MB
bitmap(位图)
比如我们在项目中,记录日活跃用户时,会需要存储一些 boolean类型数据。为了解决这个问题,redis提供了位图。bitmap同样属于String类型,每次能存储,8 个位 = 1个字节,1024个字节 = 1 KB,1024 KB = 1MB,即一个MB可以存储 8388608 条记录,属于String类型,由多个字节组成。
public void recordDAU(int userId) {String redisKey = RedisKeyUtil.getDauKey(df.format(new Date()));redisTemplate.opsForValue().setBit(redisKey, userId, true);}

位图结构使用位来实现储存信息,大大降低了内存空间使用
我们来看下位图的常用命令:
SETBIT key offset value# 如上个例子key = dau:2020:3:10# 用户访问就将已用户 id 为小标的位置 1 即可userId = 5118133
命令描述
- 针对 key 存储的字符串值,设置或清除指定偏移量 offset 上的位 (bit)
- 位的设置或清除取决于 value 值,即1或0
- 当key不存在时,会创建一个新的字符串。而且这个字符串的长度会伸展,直到可以满足指定的偏移量 offset(0 ≤offset< 2^32),在伸展过程中,新增的位的值被设置为0
key在原始状态下,所有的位都为 0
在真的做业务的时候,一般不使用uuid作为offset,这样消耗的内存太大了。
hash哈希散列
hash散列是由字符串类型的field和 value组成的映射表,可以理解为一个包含了多个键值对的集合
127.0.0.1:6379> hmset user:1 id:1 name:caoOK127.0.0.1:6379> hgetall user:11) "id:1"2) "name:cao"
hash存储结构如下图所示
hash的底部数据结构
- 数据量较少时(字符串总长度 < 64,键值对 < 512) 使用
ziplist - 其他情况,使用
dict(HashTable)
list列表
相当于linkedList,是一个链表而非数组,插入删除为O(1),查询为O(n)
127.0.0.1:6379> LPUSH biancheng Java(integer) 1127.0.0.1:6379> LPUSH biancheng Python(integer) 2127.0.0.1:6379> LRANGE biancheng 0 -11) "Python"2) "Java"
底层数据结构:
quicklist进行存储quicklist是由双向链表和ziplist共同组成ziplist:是 Redis 为节省内存而开发的,它是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表了可以包含任意多个节点,每个节点都可以保存一个字符数组或者整数值。
127.0.0.1:6379> Lpush yby gf(integer) 1127.0.0.1:6379> Lrange yby 0 -11) "gf"#从右侧插入127.0.0.1:6379> Rpush yby lv(integer) 2127.0.0.1:6379> Rpush yby haGF(integer) 3127.0.0.1:6379> lrange yby 0 -11) "gf"2) "lv"3) "haGF"# 在 haGF 前面插入 dd127.0.0.1:6379> linsert yby before haGF dd(integer) 4127.0.0.1:6379> lrange yby 0 -11) "gf"2) "lv"3) "dd"4) "haGF"# 从左侧弹出127.0.0.1:6379> Lpop yby"gf"# 从右侧弹出127.0.0.1:6379> rpop yby"haGF"
可以作为异步队列使用:将任务对应的字符串存入redis,然后进行轮询,从其中读取任务
set集合(可自动去重)
127.0.0.1:6379> SADD www.biancheng HTML(integer) 1127.0.0.1:6379> SADD www.biancheng Pandas(integer) 1127.0.0.1:6379> SMEMBERS www.biancheng1) "HTML"2) "Pandas"
数据结构:
两种相结合的底层存储方式intset和hash table,当结合内保存的 ( 所有成员都是整数值 / 保存的成员数量不超过 512 个),此时使用intset。否则使用hash tableintset的实际结构,content是一个数组
zset有序集合
和
set的区别:
- 是有序排列的
- 成员都是字符串类型
- 每个成员都关联了一个
double类型的score,从而实现了对于成员的排序
底层数据结构:
ziplist压缩列表(list)中的
(1)成员数量 < 128 个
(2)每个成员的字符串长度小于 64 字节
zlbyte是标识当前占用的总字节数zltail标识尾部元素相对于起始元素的偏移量zllen是指ziplist中entry的数量entry用来存放具体的数据项zllend标识内存结束点

skiplist:也就是跳表
其时间复杂度为O(nlogn),比如删除、插入、查找的时间复杂度
# 在有序列表中添加 score / element> zadd song 1 heyJude 2 seeyouagain(integer) 2#获取列表中的元素> zrange song 0 -11) "heyJude"2) "seeyouagain"# 从一个相反的顺序返回> zrevrange song 0 -11) "seeyouagain"2) "heyJude"# 查看某个元素的评分> zscore song heyJude1.0
HyperLogLog
HyperLogLog实际上是一个计数器,我们的项目中有用到
// 这里虽然是使用 Java 语言,但是思路是一样// 获取独立访客public void record(String ip){// 这里获得 以日期为 key 的 RedisKeyString redisKey = RedisUtil.getUvKey(df.format(new Date()));// 传入 ipredisTemplate.opsForHyperLogLog().add(redisKey, ip);}public long calculateUV(Date start, Date end){// 首先使用一个 List 来保存时间段内的 keyList<String> keyList = new ArrayList<>();Calendar calendar = Calendar.getInstance();calendar.setTime(start);while(!calendar.getTime().after(end)){String key = RedisUtil.getUvKey(df.format(calendar.getTime()));keyList.add(key);calendar.add(Calendar.DATE, 1);}// 合并数据String redisKey = RedisKeyUtil.getUvKey(df.format(start),df.format(end));redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());// 返回统计结果return redisTemplate.opsForHyperLogLog().size(redisKey);}
在redis客户端中相当于下面的操作
# 添加元素Pfadd day:2020-5-11 yby(integer) true> Pfadd day:2020-5-11 ddd(integer) true> Pfadd day:2020-5-11 sx(integer) true> Pfadd day:2020-5-12 sx(integer) true> Pfadd day:2020-5-12 PDDD(integer) true> Pfcount day:2020-5-11(integer) 3# 合并两个 key> pfmerge day:2020-5-11 day:2020-5-12(integer) 1# 计算二者的个数(去重)> pfcount day:2020-5-11(integer) 4
即HyperLogLog适用于计算基数,但是并不存储数据的内容,主要用于大数计算。
应用场景:
- 和
bitmap配合使用,bitmap标识活跃,**hyperLogLog**用于计数 - 所占用的内存较少,一个
**hyperloglog**的**key**一般占用 12k 内存

