Redis Basic
Redis的定位于特性
1. SQL与NoSQL
-
- 关系型数据库特点:
-
-
- 以表格的形式,基于行存储数据,是一个二维的模式
- 存储的是结构化的数据,数据存储有固定的模式,数据需要适应表结构
- 表与表之间存在关联
- 大部分关系型数据库都支持SQL(结构化查询语言)的操作,支持复杂的关联查询
- 通过事务(ACID酸)来提供严格或实时的数据一致性
-
-
- 关系型数据库的一些限制
-
-
- 实现扩容的话,只能垂直扩容,比如磁盘限制了数据存储,就扩大磁盘容量通过推硬件的方式,不支持动态扩容。水平扩容需要复杂的技术实现,比如分库分表
- 表结构修改困难,存储的数据格式也会收到限制
- 关系型数据库通常会把数据持久化到磁盘,在高并发和搞数据量的情况下,磁盘的读写压力会比较大
-
-
- 非关系型数据库的特点:
-
-
- 我们一般把它叫做“non-relational”或者“Not Only SQL”,NoSQL不提供SQL(Structure Query Language 结构化查询语言)
- 特点:
-
-
-
-
- 存储非结构化的数据,比如文本、图片、音频、视频;
- 表与表之间没有关联,可扩展性强;
- 保证数据的最终一致性,遵循BASE理论;Basically Available(基本可用);Soft-state(软状态);Eventually Consistent(最终一致性)
- 支持海量数据的存储和高并发的高效读写
- 支持分布式,能够对数据进行分片存储,扩缩容简单
-
-
-
-
- 常见的非关系型数据库
-
-
-
-
- KV存储:Redis和MemCached
- 文档存储:MongoDB
- 列存储:HBase
- 图存储:Neo4j
- 对象存储:
-
-
-
-
- NewSQL:结合了SQL和NoSQL的特性,例如TiDB(PingCAP)、ScaleDB
-
-
- Memcached和Redis的主要区别是什么?
-
-
- Memcached只存储KV、没有持久化机制、不支持主从复制、是多线程的
-
Redis基本数据类型
Redis默认有16个库(0-15)可在配置文件中修改,redis的存储我们叫做key-value存储,或者叫字典结构。key的最大长度限制是512M
1. String 类型
-
- 可以用来存储int(整数)、float(单精度浮点数)、String(字符串)
- 数据模型
-
-
- redis 最外层是一个redisDb结构体,里面放的是dict
- 在redis中每个键值对都是一个dictEntry,其中,key存储的是关键字定义,val存储是value定义,next指向下一个键值对
- 以set hello world为例
-
-
-
-
- 因为key是字符串,redis自己实现了一个字符串类型,叫做SDS,所以hello 指向SDS的结构。
- value是world,也是一个字符串,但是没有直接指向SDS,而是存储在redisObject中。5种常见的数据类型的value都是通过redisObject存储,最终redisObject再通过指针指向一个实际的数据结构
- redis中string类型实际对外都是string且用的string命令,但是内部却又3中不同的编码:
-
-
-
-
-
-
- int,存储8个字节的长整型(long,2^64-1)
- embstr,代表embstr格式的SDS,存储小于44个字节的字符串
- raw,存储大于44个字节的字符串
-
-
-
-
-
-
- 常见问题:
-
-
-
-
-
-
- SDS是什么?
-
-
-
-
-
-
-
-
- 是redis中字符串的实现,Simple Dynamic String,简单动态字符串。本质上还是字符串数组,但是多了一些长度等等定义。
-
-
-
-
-
-
-
-
- 为什么redis要用SDS实现字符串?
-
-
-
-
-
-
-
- embstr和raw编码的区别?为什么要为不同的大小设计不同的编码?
-
-
-
-
-
-
-
-
- embstr的使用只分配一次内存空间(因为redisObject和SDS是连续的),而raw需要分配两次内存空间(分别为redisObject和SDS分配空间)。
- 与raw相比,embstr的好处是在创建时少分配一次空间,删除时少分配一次空间。而且数据是连在一起方便寻找。
- embstr的坏处,字符串的长度增加时需要重新分配内存,redisObject和SDS都需要重新分配,因此redis中embstr实现为只读,这种编码的内容是不能修改的
-
-
-
-
-
-
-
-
- int和embstr什么时候转为raw?
-
-
-
-
-
-
-
-
- int数据不再是整数—–raw
- int大小超过了long的范围(2^63-1)—–embstr
- embstr字节超过了44字节—–raw
-
-
-
-
-
-
-
-
- 明明没有超过44字节,为什么变成了raw?
-
-
-
-
-
-
-
-
- 前面说过,对于embstr,由于实现只是可读的,因此在对embstr对像进行修改时都会先转化为raw再进行修改。因此,只要是修改embstr对象,随后都会变成raw,无论是否到达了44个字节
-
-
-
-
-
-
-
-
- 当长度小于阈值时,会还原吗?
-
-
-
-
-
-
-
-
- 不会,编码转换在redis写入数据时完成,且转换过程不可逆,只能从小内存编码到大内存编码转换
-
-
-
-
-
-
-
-
- 为什么要对底层的数据结构使用redisObject进行一层封装呢?
-
-
-
-
-
-
-
-
- 无论是设计redisObject还是设计这么多的SDS,都是为了在存储不同的内容选择不同的存储方式,这样可以尽可能的节省内存空间和提升查询速度
-
-
-
-
2. Hash 类型
-
- Hash用来存储多个无序的键值对。最大的存储数量2^32(大概40亿左右);注意hash的value只能存储字符串,不能嵌套其他类型;field不能单独设置过期时间;
- 数据模型
-
-
- Hash类型的底层使用两种数据结构实现:ziplist 和 hashtable
-
-
-
-
- ziplist是一个经过特殊编码的,由连续的内存块组成的双向链表。但是他不存储上一个节点和下一个节点的指针。而是存储上一个节点的长度和当前节点的长度。这样读写可能会慢一点,但是节省内存。
-
-
-
-
-
-
- 什么时候使用ziplist存储?当hash对象同时满足以下条件时,使用ziplist编码:
-
-
-
-
-
-
-
-
- hash对象保存的键值对数量<512个
- 所有键值对的键和值的字符串长度都<64字节(一个英文字母一个字节)
-
-
-
-
-
-
-
- hashtable数据结构
-
-
-
-
-
-
- 如果超过上面的阈值的任何一个,存储结构就会转化为hashtable
- hashtable的本质就是一个KV的结构,是一个数组 + 链表的结构!!!
- 前面我们知道redis的KV结构是都是通过dictEntry来实现的。但是,在hashtable中,又对dictEntry进行了多层封装。从最高层到最底层:dict->dictht->dictEntry
- 整体数据结构就是数组+链表。
-
-
-
-
-
-
-
-
- 为什么要定义两个哈希表(dictht[2]),其中一个不用呢?hash默认使用ht[0],ht[1]不会分配空间和初始化。ht[1]用来扩容迁移。
- 什么时候触发扩容?
-
-
-
-
-
-
-
-
-
-
- 具体和HashMap一样,由扩容因子控制
-
-
-
-
-
3. List 集合类型
-
- 存储有序的字符串(从左到右),元素可以重复,最大的存储数量2^32-1(40亿左右)
- 数据模型:
-
-
- 3.2版本后统一用quicklist来存储。quicklist存储了一个双向链表,每个节点都是一个ziplist。
-
4. Set 集合类型
-
- Set存储String类型的无序集合,最大存储数量2^32-1(40亿左右)
- 数据模型:
-
-
- Redis使用intset或者hashtable存储set
-
-
-
-
- 如果数据都是整数类型就用intset类型,否则用hashtable(数组+链表);如果元素超过512个也会用hashtable存储
-
-
5. Zset 有序集合
-
- Zset存储有序的元素,每个元素都有个score,按照score从小到大排名。score相同时,按照key的ASCLL码排序。
- 数据结构对比。
- 默认使用ziplist编码,在ziplist内部,按照score排序递增来存储。插入的时候要移动之后的数据。
- 如果元素数量大于等于128个,或者任一member长度大于等于64字节,则使用skiplist + dict存储
- 什么事skiplist(跳表)?
-
-
- 使用有序链表的问题?我们查找某个元素时,需要从头到尾逐个比较,时间复杂度O(n),当我们要插入时,同样要经历同样的过程。因此,skiplist可以解决上述问题。
- 查找过程:
-
-
-
-
- 当我们查找数据时,可以先沿着新链表查找。当碰到比待查找数据大的节点,再到下层查找。
- 总结,这个查找过程中,由于新增指针我们不需要与链表每个节点逐个比较了,需要比较的节点大概只有原来的一半,这就是跳表。为什么不用AVL树或者红黑树?因为跳表更简洁。
-
-
-
总结:
-