目录标题
- 数据类型
- String
- 优点
- 缺点
- 底层结构
- 使用场景
- 实际使用
- List
- 优点
- 缺点
- 底层结构
- 使用场景
- 实际使用
- Hash
- 优点
- 缺点
- 底层结构
- 使用场景
- 实际使用
- Set
- 优点
- 缺点
- 底层结构
- 使用场景
- 实际使用
- Zset
- 优点
- 缺点
- 底层结构
- 使用场景
- 实际使用
- BitMap
- Stream
- 类型抉择
- 空间占用
- 数据特征
- 数据删除策略
- 定时删除
- 惰性删除
- 定期删除
- 数据淘汰策略
数据类型
redis中value的最大容量默认是 512M,各个数据类型初始容量如下:
- String:512M
- Set,ZSet,List,Hash:2^32 -1 个元素
总结:任何系统都是在内存和性能之间不断抉择,使用最少的内存提供可接受的反应速度。redis亦如此。
String
优点
简单直观,便捷存储单个元素/散列元素。提供字符串函数,append等
缺点
占用过多的键,内存占用量较大,同时用户信息内聚性比较差。难以管理
底层结构
简单动态字符串(simple dynamic string),字节数组+未用空间+已用空间,预分配空间,方便快速扩容及确定字段长度,牺牲空间换时间
使用场景
可以存储任意类型的数据,包括文本、数字等。
实际使用
- 热点用户个人信息
- 库存计数器
- 分布式锁
- 项目奖品策略
- 广告点击次数
- 验证码
List
优点
存储有序数据,支持从列表的两端进行元素的插入和删除操作。
缺点
数据耦合高,取内部元素相对耗时
底层结构
老版本是双向链表+压缩列表,双向链表即pre+value+next,压缩链表即分配一段连续内存,省掉pre和next的损耗,前者适合大量元素,后者适合少量元素;
新版本采用快速链表,是前两者的融合,pre+小型双向链表+next,即将长度较大的双向链表进行分段存储。
- 压缩列表ziplist (插入元素过多或字符串太大,就需要调用 realloc 扩展内存 )
- 双向链表linkedlist (需附加指针prev 和 next,较浪费空间,加重内存的碎片化 )
因为双向链表占用的内存比压缩列表要多,所以当创建新列表时,会优先考虑使用压缩列表,并且在有需要的时候才从压缩列表实现转换到双向链表实现。
压缩列表转化成双向链表条件有两个:
- 列表中某个字符串值超过 list_max_ziplist_value(默认64字节 )
- 列表的元素个数超过 list_max_ziplist_entries(默认 512个 )
使用场景
有序消息,或者固定容量数据。例如消息队列、最新消息排行
实际使用
消息队列虽然可以但不建议因为redis会丢消息,它是缓存不是持久存储,除非是不重要的消息;
- 广告展示,默认展示最新条数,设置长度固定的list,每次从顶端插入,从尾端剔除元素;
- 中奖名单,同样固定容量,顶端插入提出尾部元素。
Hash
优点
通过一个key对应多个字段和对应的值,适用于存储对象属性、配置信息等复杂数据结构。
缺点
数据占用空间过大时增删耗时旧,甚至引起reids卡顿
底层结构
压缩列表+hashtable,与list的压缩列表不同的是,它存储的不仅是feild+value交替存储;hashtable即java的hashmap,数组+链表。
渐进式扩容:新老hashtable交替执行,即旧数据慢慢同步至新hashtable中。
新建hash值时优先使用压缩列表实现,压缩列表使用更加紧凑的连续内存结构可以节省空间,其读写复杂度为O(n);当数据量过大或者字段过多读写效率减低,此时会转化为hashtable,占用更多的内存,读写复杂度降低为O(1)。转化条件:
- 哈希类型元素个数大于hash-max-ziplist-entries配置(默认512个)
- 有字段值大于hash-max-ziplist-value配置(默认64字节)
使用场景
总是需要获取单一属性的值,数据既耦合又方便存取。
实际使用
- 地理位置信息对应关系
- 项目配置信息
Set
优点
无序集合,可以存储多个字符串元素,并提供高效的集合操作,如交集、并集、差集等。
缺点
大量数据会导致redis卡顿
底层结构
intset+hashTable;采用hashTable存储,只是value统一设置为null,为什么不用ziplist来节省空间是因为必须保证元素的唯一性。
inset可理解为数组,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:
- 对象保存的所有元素都是整数值(int)
- 对象保存的元素数量不超过512个
使用场景
需要进行集合运算的数据。
- 例如文章标签管理-可根据多个标签聚合查询文章;
- 社交网络好友关系-将每个用户的好友列表存储在Redis的集合中,使用集合操作可以快速判断两个用户是否是好友,还可以进行好友推荐等功能。
实际使用
- 经销商的门店服务列表,业务员可以交叉负责经销商底下的门店,利用经销商和门店的归属关系可以去重聚合出经销商的门店列表;
- 策略集聚合,一个转盘会对应多个策略集,通过条件判断出新老用户概念,确定出需要聚合哪些的策略。
Zset
优点
存储多个字符串元素,并为每个元素关联一个分数,支持按照分数进行排序和范围查找。
缺点
-
内存开销:Zset是基于跳表和哈希表实现的,存储数据时,会占用比简单的集合或列表更多的内存。因此,在存储较大规模的数据时,内存开销可能会显著增加。
-
性能开销:在一些特定操作中,如按得分范围查询(ZRANGEBYSCORE)或根据排名进行范围查询(ZRANGE),操作的复杂度为O(log(N) + M),其中N是集合的大小,而M是返回的小集合大小。这与其他类型的数据结构相比,可能在性能上有所逊色,尤其是在处理大数据量时。
-
数据排序:虽然Zset提供了自动排序功能,但在某些情况下(如频繁更新得分),这可能导致性能下降,尤其是在需要多次更新和读取的场景下。
底层结构
hashtable+skiplsit,hashtable存储k-score, skiplist中的每个node存储 层级+value+score,插入时按照score排序。
当元素数量不多时,hashtable和SkipList的优势不明显,而且更耗内存。因此zset还会采用ZipList结构来节省内存。
查询效率和红黑树相当。
使用场景
需要排序的数据
实际使用
业务员的补货数量排名。
BitMap
Bitmap数据类型可以看作是一种特殊的字符串,它所占用空间中的每一个bit都只能是0或1。Redis内部将每个字符(bit)作为一个元素来处理,因此我们可以在非常小的空间中存储大量的位信息。这使得Bitmap非常适合于存储和处理大规模的布尔型信息,如用户的在线状态、活跃用户、用户访问记录等。
统计消费者的参与情况,和业务员的工作情况。
Stream
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
类型抉择
- 多个集合操作(聚合操作)----用Set;
- 集合数据排序(排序操作) ---- 分页排序建议使用ZSet;
- 集合数据只有0、1两种状态(二值型数据)----------- 0/1状态数据建议使用Bitmap;
- 集合中不重复元素个数(基数统计)----如果数据量达到亿级的话建议使用HyperLogLog。
当然以上都是套话,任何抉择都一样,搞清楚自己的需求,对症下药即可,当你对各种数据类型的特性了解之后,自然就可以做出最正确的选择。何时何地何事都如此。
空间占用
以String类型计算,k是tc:jdb:/UZJXTIP0CTZ6L0 v是32010670;
- 2.4kw 5G
- 1ww 25G
总结起来就是 1个kv占用218B。而在计算机中,一个英文字母/整数通常占用1个字节(1 B)。这是因为在使用 ASCII 编码时,单个英文字母(无论是大写还是小写)或者整数都可以用一个字节表示。
所以redis的空间占用大概是字节数的7倍。
数据特征
Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态。TTL返回的值有三种情况:正数,-1,-2
- 正数:代表该数据在内存中还能存活的时间
- -1:永久有效的数据
- -2:已经过期的数据 或被删除的数据 或 未定义的数据
redis中的过期时间是单独存储的,Hash结构,field是内存地址,value是过期时间,保存了所有key的过期描述,在最终进行过期处理的时候,对该空间的数据进行检测, 当时间到期之后通过field找到内存该地址处的数据,然后进行相关操作。
数据删除策略
删除策略就是针对已过期数据的处理策略,已过期的数据是真的就立即删除了吗?其实并不是,redis有多种删除策略,在不同的场景下使用不同的删除方式会有不同效果。
不同的删除策略就是对内存占用与CPU占用之间的不同侧重。目前有三种删除策略:
- 定时删除:时间换空间,通过对cpu的无间断占用节约内存
- 惰性删除:空间换时间,通过对内存的占用节约cpu资源
- 定期删除:折中方案,花费少量cpu资源节约少量内存
定时删除
创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
惰性删除
数据到达过期时间,不做处理,等该数据被访问时进行过期判断。如果未过期,直接返回数据,如果已过期,先删除数据,再返回数据不存在
定期删除
定时删除和惰性删除这两种方案都是走的极端,那有没有折中方案?是的,redis还引入了一种定期删除策略,进行了空间和性能的折中处理
已知redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,该方案是默认每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
- 从过期字典中随机 20 个 key;
- 删除这 20 个 key 中已经过期的 key;
- 如果过期的 key 比率超过 1/4,那就重复步骤 1;