Redis 数据类型
一:redis 定位和特性
在大部分时候,我们优先考虑使用关系型数据库还存储业务数据,比如:Mysql
关系型数据库特点:
-
1、基于行存储数据,二维的模式
-
2、存储结构化的数据,数据存储有固定的模式(schema)
-
3、表与表之间存在关联(Relationship)
-
4、大都支持SQL(结构化查询语言)的操作,支持复杂的关联查询
-
5、通过支持事务ACID来提供严格或者实时的数据一致性
但是关系型数据库页存在一些限制:
- 1、要实现扩容的话,只能向上(垂直)扩展,不支持动态的扩缩容
- 2、表结构修改困难,因此存储的数据格式也受到限制
- 3、高并发情况下,基于磁盘的读写压力比较大
为了规避这一系列问题,所以就有了非关系型数据库(non-relational)。
非关系型数据库特点:
- 1、存储非结构化的数据,比如文本、图片、音频、视频
- 2、表与表之间没有关联,可扩展性强
- 3、保证数据的最终一致性,遵循BASE 理论
- 4、支持海量数据的存储和高并发的高效读写
- 5、支持分布式,能够对数据进行分片存储,扩缩容简单
对于不同的存储类型,就有了各种各样的非关系型数据库:
- 1、KV存储:Redis和Memcached
- 2、文档存储:MongoDB
- 3、列存储:HBase
- 4、图存储:Neo4j
- 5、对象存储:Oss
Redis基本特点
为什么把数据放入内存中?
- 内存访问的速度快,10w qps
- 减少计算时间,减轻数据库压力
Redis基本特点:
-
速度快
-
支持多种数据类型
-
支持多种编程语言
-
持久化、内存淘汰
-
功能丰富:事务、发布订阅、pipeline、lua
-
集群、分布式
二:redis 基本数据类型
2.1、String-存储类型
String可以储存:
- 1、INT 整型
- 2、Float 单精度浮点型
- 3、String 字符串
底层:C语言中没有字符串类型,String用char[]数组表示,源码中用SDS(simple dynamic string)封装char[],这是是Redis存储的最小单元,一个SDS最大可以存储512M信息。
struct sdshdr{
unsigned int len; // 标记char[]的长度
unsigned int free; //标记char[]中未使用的元素个数
char buf[]; // 存放元素的坑
}
为什么Redis要用SDS实现字符串?
- 1、内存空间预先分配
- 2、获取字符长度的时间复杂度O(n)
- 3、长度变更引起内存重新分 配
- 4、用’\0’判断字符串结束
- 5、惰性释放空间,
Redis对SDS再次封装生成了RedisObject,实际上五种常用的数据类型的任何一种的value,都是通过RedisObject来存储的。
当你执行set hello world的时候,其实Redis会创建两个RedisObject对象,键的RedisObject 和 值的RedisOjbect 其中它们type = REDIS_STRING,而SDS分别存储的就是 hello 跟 world字符串。
虽然对外都是String,用的也是String命令,但是会出现三种不同的编码:
- 1、int ,储存8个字节的长整型(2^63-1)
- 2、embstr,存储小于44个字节的字符串
- 3、raw,长度超过了44个字节
转换:
- 1、int 数据不再是整数——raw
- 2、int大小超过了 long 的范围(2^63-1)——embstr
- 3、embstr长度超过了44个字节——raw
编码转换在 Redis 写入数据时完成,且转换过程不可逆, 只能从小内存编码向大内存编码转换(不包括重新set)
String应用场景
1、缓存,key-value存储
2、分布式Session (这种很少,目前都是JWT)
3、分布式锁 set NX EX
4、分布式全局ID incr
5、计数器 incr
6、限流 incr
7、位操作
2.2、Hash 哈希
存储多个无序的键值对,最大存储数量2^32-1(40亿左右)。
Hash的value只能是字符串,不能嵌套其他的类型,比如hash、list。
String与Hash的区别或者Hash的特点
- 1、节省内存空间
- 2、减少key冲突
- 3、取值减少性能消耗
Hash不适合的场景:
- 1、Field不能单独设置过期时间
- 2、需要考虑数据量分布的问题
redis的Hash本身也是K-V的结构,底层是使用了两种存储数据结构实现的
- ziplist:OBJ_ENCODING_ZIPLIST(压缩列表)
- hashtable:OBJ_ENCODING_HT(哈希表)
ziplist是一个经过特殊编码的,由连续内存块组成的双向链表。 它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存 储上一个节点长度和当前节点长度。
Hash-什么时候用ziplist?
- 1、一个hash对象保存的field数量<512个
- 2、一个hash对象中所有的field和value的字符串长度都<64byte
HashTable(dict)
从自底层到最高层 dictEntry —>dictht—>dict,它是 一个数组+链表的结构 。
Hash应用场景
- 1、String能做的,Hash都可以做
- 2、存储对象类型的数据
- 3、非常适用于将一些相关的数据存储在一起,比如用户的购物车。该类型在日常用途还是挺多的。
2.3、List
-
存储有序的字符串(从左到右),元素可以重复
-
最大存储数量2^32-1(40亿左右)
List目前统一使用quicklist来存储,底层就是个数组+链表的结构 ,常用就这几个组合。
lpush + lpop = stack 先进后出的栈
lpush + rpop = queue 先进先出的队列
lpush + ltrim = capped collection 有限集合
lpush + brpop = message queue 消息队列
list-应用场景
- 列表: 消息列表 文章列表 评论列表 活动列表
- 简单的消息队列
2.4、set集合
- Set存储String类型的无序集合。
- 最大存储数量2^32-1(40亿左右)。
redis set-存储结构
- 1、intset
- 2、hashtable
如果是整数类型是用intset存储,不是整数类型就用hashtable(数组+链表),如果元素个数超过512个也会用hashtable
set-应用场景
- 1、抽奖:spop myset
- 2、点赞、签到、打卡(用户记录)
- 3、商品标签、用户画像(人口属性、信用属性、社交、兴趣爱好…)
- 4、用户关注、推荐模型(相互关注? 我关注的人也关注了他? 可能认识的人?)
2.5、zset 有序集合
zset-存储结构
- ziplist(元素数量<128,所有元素长度小于64bytes)
- skiplist + dict
skiplist跳表就是多层链表的结合体,跳表分为许多层(level),每一层都可以看作是数据的索引,这些索引的意义就是加快跳表查找数据速度
zset-应用场景
- 积分排行榜
- 时间排序新闻
- 延时队列(jesque)
2.6、Bitmap:
是用一个比特位来映射某个元素的状态。由于一个比特位只能表示 0 和 1 两种状态,所以 BitMap 能映射的状态有限,但是使用比特位的优势是能大量的节省内存空间。
Bitmap-应用场景
-
用户签到
-
统计活跃用户
2.7、Geospatial:
核心思想就是将地球近似为球体来看待,然后 GEO利用 GeoHash 将二维的经纬度转换成字符串,来实现位置的划分跟指定距离的查询
应用场景:经纬度来实现位置的划分跟指定距离的查询
2.8、Hyperloglogs
是一种概率数据结构,它使用概率算法来统计集合的近似基数。
应用场景:误差允许范围内做基数统计 (基数就是指一个集合中不同值的个数) 的时候非常有用,每个HyperLogLog的键可以计算接近2^64不同元素的基数,而大小只需要12KB。错误率大概在0.81%。所以如果用做 UV 统计很合适。
2.9、streams
redis 5.0 推出的数据类型,支持多播的可持久化队列,用于实现发布订阅功能,借鉴了kafka 的设计
2.10、Bloom Filter
当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点(有效降低冲突概率),把它们置为1。检索时,我们只要看看这些点是不是都是1就知道集合中有没有它了:如果这些点有任何一个为0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
以上图为例,假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
使用布隆过滤器得到的判断结果:不存在的一定不存在,存在的不一定存在。