1. 数据类型
一、String
String是redis中最常用的数据类型,一个key对应一个字符串。
1.1 使用场景
- 缓存:将数据序列化为字符串,存储在redis中
- 记录token
- 计数器
- 分布式锁
1.2 常用命令
命令 | 描述 |
---|---|
SET key value | 设置key的值 |
GET key | 获取key的值 |
GETRANGE key start end | 获取指定key的字符串的子串,start和end指定子串起始和结束位置 |
GETSET key value | 将key对应的值设为value,并返回key的旧值 |
MGET key1 [key2..] | 获取一个或多个key的值 |
SETEX key seconds value | 设置key的值,并设置key的过期时间为seconds秒 |
SETNX key value | 只有在key不存在时,设置key的值 |
STRLEN key | 获取key的值的字符串长度 |
MSET key value [key value ...] | 同时设置一个或多个key的值 |
MSETNX key value [key value ...] | 同时设置一个或多个key的值,当且仅当所有给定 key 都不存在 |
PSETEX key milliseconds value | 跟setex一样,只不过过期时间的单位是毫秒 |
INCR key | 将key中储存的数字值加一 |
INCRBY key increment | 将key 所储存的值加上给定的增量值(increment) |
DECR key | 将key中储存的数字值减一 |
DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) |
APPEND key value | 如果 key 已经存在并且是一个字符串, 将value追加到key的值的末尾 |
1.3 底层实现
字符串对象的编码可以是int、raw或者embstr
- int: 如果一个字符串对象保存的是整数值,redis底层会用long去存储
- raw: 如果存的是字符串,并且这个字符串值的长度大于32字节,那么使用SDS保存这个字符串
- embstr: 小于等于32字节时,也是SDS
redis的字符串表示为sds(simple dynamic string),而不是C语言的字符串(以\0结尾的char*)。
1.3.1 sds组成
- len: 字符串长度
- buf[]: 字符数组,用来保存实际数据
- alloc: 分配给字符数组的空间长度。修改字符串时,可以通过 alloc - len判断剩余空间是否足够,不足够时sds会扩容
- flags: 用来表示不同类型的SDS。sds有5种类型: sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64。区别就在于,它们len和alloc的数据类型不同。
1.3.2 为什么不用c语言的的字符串
- 获取字符串长度的时间复杂度是O(n)。 而sds有一个专门存储字符串长度的属性len,时间复杂度O(1)
- 二进制不安全。c语言用\0标识字符串结尾,而sds不依赖\0(以为属性len,知道了字符串长度), 因此不会因为数据包含\0而导致数据出问题
- 缓冲区溢出。拼接字符串时,C语言使用strcat拼接,当没有分配足够长度的内存空间,就会造成缓冲区溢出
二、List
Redis中的List是简单的字符串List,List里面存储的都是String类型。
2.1 使用场景
- 模拟栈
lpush key value #入栈
lpop key #出栈
- 模拟队列
lpush key value #入队列
rpop key #出队列
2.2 常用命令
命令 | 描述 |
---|---|
RPUSH key value | 将value添加到列表右端(尾部) |
LPUSH key value | 将value添加到列表左端(头部) |
RPOP key | 从列表的右端(尾部)的值弹出并返回弹出的值 |
LPOP key | 从列表的左端(头部)的值弹出并返回弹出的值 |
LRANGE key start stop | 获取列表中指定范围的值 |
LINDEX key index | 获取列表中指定位置的值 |
LTRIM key start stop | 只保留列表中指定区间内的元素 |
BLPOP key timeout | 从列表的左端(头部)的值弹出并返回弹出的值,如果列表中没有元素,就等待,直到超时或有元素可以弹出为止 |
2.3 底层实现
- 双向链表
- 压缩列表
- 快表 quicklist
2.3.1 双向链表
typedef struct list {
//链表头节点
listNode *head;
//链表尾节点
listNode *tail;
//节点值复制函数
void *(*dup)(void *ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值比较函数
int (*match)(void *ptr, void *key);
//链表节点数量
unsigned long len;
} list;
优点:
- 因为是双向链表,所以获取某个节点的前置节点或后置节点的时间复杂度只需O(1)
- 因为存储了头结点、尾结点指针,所以获取时的时间复杂度只需O(1)
- 因为存储了len,所以获取链表长度的时间复杂度只需O(1)
缺点:
- 链表的节点之间在内存中不一定连续,没有数组访问起来快速
- 链表的节点存储开销比较大,比较占内存
2.3.2 压缩列表(ZipList)
压缩列表是一种内存紧凑型的数据结构。其占用一块连续的内存空间,而且会针对不同长度的数据,进行相应编码,能够有效地节省内存开销。
优点:
- 连续的内存空间。可以利用 CPU 缓存,访问快速
- 针对性的编码。能够节省内存开销
缺点:
- 不能保存过多的元素,否则查询效率就会降低
- 新增或修改某个元素时,压缩列表占用的内存空间需要重新分配,甚至可能引发连锁更新的问题,影响数据访问
压缩列表只适用于元素数量较少的情况
2.3.3 快表 quicklist
快表是一个数组链表,为什么这么说?因为它的链表节点是一个压缩列表
三、Set
Redis中的Set是无序字符串集合,集合中的成员是唯一的。
3.1 使用场景
- 点赞:使用set存储点赞用户的uid,保证一个用户只能点一个赞
sadd key uid #点赞(/收藏)
srem key uid #取消点赞(/收藏)
smembers key # 获取所有点赞(/收藏)用户
scard key # 获取点赞用户数量
sismember key uid #判断是否点赞(/收藏)
- 好友关系: 使用set计算交集、差集等
sadd uid1 1 2 3 4 5
sadd uid2 4 5 6 7 8 --某个user的好友id放入集合
sinter uid uid2 --获取共同好友
sdiff uid uid2 --给user2推荐user1的好友
sismember uid1 5
sismember uid2 5 --验证某个用户是否同时被user1和user2关注
3.2 常用命令
命令 | 描述 |
---|---|
SADD key value1 [value2] | 向集合添加一个或多个成员 |
SREM key value1 [value2] | 移除集合中一个或多个成员 |
SCARD key | 获取集合成员数 |
SMEMBERS key | 获取集合中的所有值 |
SMEMBERS key member | 判断member是否是在集合中 |
SINTER key1 [key2] | 返回给定集合的交集 |
SDIFF key1 [key2] | 返回第一个集合与其他集合之间的差集 |
3.3 底层实现
- intset 整数集合
- hashtable
intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面,hashtable跟java中的hashmap实现原理一样,不再赘述
四、Hash
Redis中的Hash是就类似于java中的map对象
4.1应用场景
- 缓存:多数场景下是将数据序列化成json字符串,存储成string类型。当有频繁读取或修改数据中的某个字段时,可以考虑使用hash存储数据,避免反复序列化、反序列化导致的性能开销。以下是一个购物车场景:
# 添加商品
hset cart:{用户id} {商品id} 1
# 增加数量
hincrby cart:{用户id} {商品id} 1
# 获取商品类型总数
hlen cart:{用户id}
# 删除商品
hdel cart:{用户id} {商品id}
# 获取购物车所有商品
hgetall cart:{用户id}
- 分布式锁: Redisson在实现分布式锁时,用的不是string,而是hash
4.2 常用命令
命令 | 描述 |
---|---|
HSET key field value | set值 |
HDEL key field | 删除值 |
HLEN key | 获取field数量 |
HINCRBY key field | 给指定的field值加一 |
HKEYS key | 获取所有field |
HVALS key | 获取所有field对应的值 |
HGETALL key | 获取所有field对应的值 |
4.3 底层实现
- 压缩列表
- hashtable
压缩列表上面讲过,hashtable跟java中的hashmap实现原理一样,不再赘述。
五、ZSet
Set是无序的,ZSet是有序的,实现有序的方式是引入score字段,通过score进行升序排列。
5.1 应用场景
- 排行榜
- 滑动窗口: 跳转
5.2 常用命令
命令 | 描述 |
---|---|
ZADD key score1 member1 | 向集合添加一个或多个成员 |
ZCARD key | 获取集合成员数 |
ZCOUNT key min max | 计算score在min~max之间的成员个数 |
ZINCRBY key increment member | 给定成员的score增加member |
ZRANGE key start stop [WITHSCORES] | 返回index在start~stop之间的成员 |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 返回score在min~max之间的成员 |
zrank key member | 获取member在集合中的正向排名 |
zrevrank key member | 获取member在集合中的逆向排名 |
5.3 底层实现
六、HyperLogLogs
HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的
6.1 应用场景
- 计数器
6.2 常用命令
命令 | 描述 |
---|---|
PFADD key element [element ...] | 添加元素到HyperLogLog 中 |
PFCOUNT key [key ...] | 返回HyperLogLog计算的元素个数 |
PFMERGE newKey key1 [key2 ...] | 合并多个HyperLogLog |
Loading...