redis的诞生以及发展历程,这里就不用多赘述了,这篇文章从Redis 基本数据类型来讲起!并且只讲存储类型以及存储的原理
1.Redis-String
1) 存储类型 : 可以用来存储字符串、整数、浮点数。
2)String 存储(实现)原理 :
1.数据模型:
以set hello word 为例,因为 Redis 是 KV 的数据库,它是通过 hashtable 实现的(我们把这个叫做外层的哈希)。所以每个键值对都会有一个 dictEntry(源码位置:dict.h),里面指向了 key 和 value 的指针。next 指向下一个 dictEntry。
其中,key 是字符串,但是 Redis 没有直接使用 C 的字符数组,而是存储在自定义的 SDS中。value 既不是直接作为字符串存储,也不是直接存储在 SDS 中,而是存储在redisObject 中。实际上五种常用的数据类型的任何一种,都是通过 redisObject 来存储的。
2. 具体分析:
1)redisObject:redisObject 定义在 src/server.h 文件中。
2)内部编码:
这里注意,*ptr指向对象实际的数据结构,在这里是字符串!可以通过 object encoding key这个命令来查看!
3)字符串类型的编码:1. int,存储 8 个字节的长整型(long,2^63-1)。2. embstr, 代表 embstr 格式的 SDS(Simple Dynamic String 简单动态字符串),存储小于 44 个字节的字符串。3.raw,存储大于 44 个字节的字符串.
3.问题分析:
1)什么是 SDS:SDS是Redis 中字符串的实现。在 3.2 以后的版本中,SDS 又有多种结构(sds.h):sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表 2^5=32byte,2^8=256byte,2^16=65536byte=64KB,2^32byte=4GB。
2)为什么 Redis 要用 SDS 实现字符串:
3)embstr 和 raw 的区别:embstr 的使用只分配一次内存空间(因为 RedisObject 和 SDS 是连续的),而 raw需要分配两次内存空间(分别为 RedisObject 和 SDS 分配空间)。因此与 raw 相比,embstr 的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而 embstr 的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个RedisObject 和 SDS 都需要重新分配空间,因此 Redis 中的 embstr 实现为只读。
4)int 和 embstr 什么时候转化为 raw:当 int 数据不再是整数,或大小超过了 long 的范围(2^63-1=9223372036854775807)时,自动转化为 embstr。
5)明明没有超过阈值,为什么修改的时候embstr变成 raw 了:对于 embstr,由于其实现是只读的,因此在对 embstr 对象进行修改时,都会先转化为 raw 再进行修改。因此,只要是修改 embstr 对象,修改后的对象一定是 raw 的,无论是否达到了 44个字节。
6)当长度小于阈值时,会还原吗:关于 Redis 内部编码的转换,都符合以下规律:编码转换在 Redis 写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换(但是不包括重新 set)
7)为什么要对底层的数据结构进行一层包装呢:通过封装,可以根据对象的类型动态地选择存储结构和可以使用的命令,实现节省空间和优化查询速度。
4.应用场景
1)缓存:例如:热点数据缓存(例如报表,明星出轨),对象缓存,全页缓存。可以提升热点数据的访问速度。
2)数据共享分布式:因为 Redis 是分布式的独立服务,可以在多个应用之间共享。例如:分布式 Session
3)分布式锁:STRING 类型 setnx 方法,只有不存在时才能添加成功,返回 true。
4)全局 ID:INT 类型,INCRBY,利用原子性 .如incrby userid 1000
5)计数器:INT 类型,INCR 方法.例如:文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库。
6)限流:INT 类型,INCR 方法,以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。
2.Redis-HASH
1)简述:
1.Hash 与 String 的主要区别:
String中value 只能是字符串,不能嵌套其他类型.而hash把所有相关的值聚集到一个 key 中,节省内存空间,只使用一个 key,减少 key 冲突,当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU 的消耗。
2.Hash 不适合的场景:
1)Field 不能单独设置过期时间 2)没有 bit 操作 3)需要考虑数据量分布的问题(value 值非常大的时候,无法分布到多个节点)
2)存储原理:
1.简述:
Redis 的 Hash 本身也是一个 KV 的结构,类似于 Java 中的 HashMap。外层的哈希(Redis KV 的实现)只用到了 hashtable。当存储 hash 数据类型时,我们把它叫做内层的哈希。内层的哈希底层可以使用两种数据结构实现:1)ziplist:OBJ_ENCODING_ZIPLIST(压缩列表) 2)hashtable:OBJ_ENCODING_HT(哈希表)。
2.ziplist 压缩列表
1)定义:ziplist 是一个经过特殊编码的双向链表,它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度,通过牺牲部分读写性能,来换取高效的内存空间利用率,是一种时间换空间的思想。只用在字段个数少,字段值小的场景里面。
2)ziplist 的内部结构?
3)什么时候使用 ziplist 存储?
当 hash 对象同时满足以下两个条件的时候,使用 ziplist 编码:1)所有的键值对的健和值的字符串长度都小于等于 64byte(一个英文字母一个字节);2)哈希对象保存的键值对数量小于 512 个。 并且一个哈希对象超过配置的阈值(键和值的长度有>64byte,键值对个数>512 个)时,会转换成哈希表(hashtable)。
3.hashtable(dict)---》内层的hash
1)定义:在 Redis 中,hashtable 被称为字典(dictionary),它是一个数组+链表的结构。
2)存储结构:从最底层到最高层 dictEntry——dictht——dict——OBJ_ENCODING_HT
3)具体的实现源码分析:从下到上!Redis 的 KV 结构是通过一个 dictEntry 来实现的。Redis 又对 dictEntry 进行了多层的封装实现内层hash。
4)问题分析-为什么要定义两个哈希表呢?ht[2]:redis 的 hash 默认使用的是 ht[0],ht[1]不会初始化和分配空间。定义俩个哈希表是为了防止rehash操作!
41)rehash 的步骤:1.为字符 ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及 ht[0]当前包含的键值对的数量。(扩展:ht[1]的大小为第一个大于等于 ht[0].used*2。) 2.将所有的 ht[0]上的节点 rehash 到 ht[1]上,重新计算 hash 值和索引,然后放入指定的位置。3.当 ht[0]全部迁移到了 ht[1]之后,释放 ht[0]的空间,将 ht[1]设置为 ht[0]表,并创建新的 ht[1],为下次 rehash 做准备。
42)什么时候触发扩容:
5)应用场景:比如对象或者一张表的数据,比 String 节省了更多 key 的空间,也更加便于集中管理。例如购物车
3.Redis-List 列表
1)存储类型: 存储有序的字符串(从左到右),元素可以重复。可以充当队列和栈的角色。
2)存储(实现)原理
1.quicklist:
1)定义:quicklist(快速列表)是 ziplist 和 linkedlist 的结合体。
2)具体实现:
3)redis.conf 相关参数:
4)quickList--->quicklistNode:quicklistNode 中的*zl 指向一个 ziplist,一个 ziplist 可以存放多个元素。
5)总体结构:
3)应用场景
1.用户消息时间线 timeline----》 因为 List 是有序的,可以用来做用户时间线
2.消息队列---》List 提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间。
1)BLPOP:BLPOP key1 timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 2)BRPOP:BRPOP key1 timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
4.Redis-Set集合
1)存储类型:String 类型的无序集合,最大存储数量 2^32-1(40 亿左右)。
2)存储(实现)原理:
Redis 用 intset 或 hashtable 存储 set。如果元素都是整数类型,就用 inset 存储。如果不是整数类型,就用 hashtable(数组+链表的存来储结构)。
1.kv如何存储set元素:key 就是元素的值,value 为 null。
2.注意点:如果元素个数超过 512 个,也会用 hashtable 存储。
3)应用场景:
1.抽奖:
随机获取元素: spop myset
2.点赞、签到、打卡
3.商品标签
4.商品筛选-
1)获取差集:sdiff set1 set2 2)获取交集:sinter set1 set2 3)获取并集:sunion set1 set2
5.Redis-ZSet 有序集合
1)存储类型:
1.定义:sorted set,有序的 set,每个元素有个 score。score 相同时,按照 key 的 ASCII 码排序
2.数据结构对比:
2).存储(实现)原理
1.原理:
同时满足以下条件时使用 ziplist 编码:1)元素数量小于 128 个 2)所有 member 的长度都小于 64 字节.并且在 ziplist 的内部,按照 score 排序递增来存储。插入的时候要移动之后的数据。超过阈值之后,使用 skiplist(跳表)+dict 存储。
2. 应用场景
1)排行榜:1.id 为 6001 的新闻点击数加 1:zincrby hotNews:20190926 1 n6001 2.获取今天点击最多的 15 条:zrevrange hotNews:20190926 0 15 withscores
6.Redis-其他数据结构
1)BitMaps
1.定义:Bitmaps 是在字符串类型上面定义的位操作。一个字节由 8 个二进制位组成。
2.应用场景:用户访问统计,在线用户统计
1)支持按位与、按位或等等操作:
2)计算出 7 天都在线的用户:BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"