远程内存数据库、非关系数据库
复制、持久化、客户端分片 -----》拓展成包含数百GB数据、每秒处理上百万次请求的系统。
主从复制:从连接上主,接收主发送的整个数据库初始化副本,之后主执行写命令时都会发送给所有从服务执行,实时更新从服务器的数据。
redis将数据存储在内存中,发送给redis的命令请求不需要经过查询分析器、查询优化器。
一. Redis数据结构
-
String 字符串
由字节组成的序列,可以是字符串、整数(long)或浮点数(double)。可对数字执行自增、自减操作(原子性的,即使多个客户端对key进行操作也不会导致竞争问题)。
Redis虽是C语言写的,但是没有直接用C的字符串 ----> 提升速度,提升性能。
简单动态字符串(SDS):struct sdshdr{ // 记录已使用长度 int len; // 记录空闲未使用的长度 int free; // 字符数组 char[] buf; };
SDS动态扩展:
(1).先计算出大小是否足够
(2).开辟空间至满足需求
(3).开辟与已使用大小len相同长度的空闲free空间(如果len < 1M)开辟1M长度的空闲free空间(如果len >= 1M)SDS性能优势:
- 快速获取字符串长度
- 避免缓冲区溢出
- 降低空间分配次数提升内存使用效率 (空间预分配与惰性空间回收)Redis 规定了字符串的长度不得超过 512 MB。
字符串存储主要的编码方式主要有两种 : embstr 和 raw (超过44字符),debug object key 来查看编码方式
常用命令:get set del append keyname value
getrange key-name start end ------> substr
自增和自减命令:incr decr incrby decrby incrbyfloat -
List 列表
链表结构,有序、可重复 可以存储多个字符串。
相当于Java中的LinkedList,插入/删除–O(1) ,索引定位O(n)常用命令:lpush/rpush lpop/rpop lindex lrange ltrim
-
Hash散列
存储多个键值对之间的映射
数组+链表 (哈希表),相当于Java中的HashMap
关系数据库,散列可看作是数据库里的行;文档数据库,散列可看作是库里的文档。实际上字典结构的内部包含两个 hashtable -----> 渐进式 rehash : 大字典的扩容是比较耗时间的,需要重新申请新的数组,然后将旧字典所有链表中的元素重新挂接到新的数组下面,这是一个 O(n) 级别的操作,作为单线程的 Redis 很难承受这样耗时的过程,所以 Redis 使用 渐进式 rehash 小步搬迁。rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个 hash 结构,然后在后续的定时任务以及 hash 操作指令中,循序渐进的把旧字典的内容迁移到新字典中。当搬迁完成了,就会使用新的 hash 结构取而代之。
扩容条件:元素的个数等于第一维数组的长度 -----> 扩为之前的2倍;若redis正在做持久化,尽量不去扩容,当达到了第一维数组长度的 5 倍了 -----> 强制扩容
缩容条件:元素个数低于数组长度的 10%
hash 也有缺点,hash 结构的存储消耗要高于单个字符串
常用命令:hset hget hgetall hdel hexists hkeys hvalues hincrby/hincrbyfloat
-
Set 集合
无序、不可重复,相当于Java中的 HashSet
它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。
常用命令:sadd srem sismember smembers sinter(交集) sunion(并集) sdiff(差集) -
Zset有序集合
和散列一样,存储的是键值对。
键被成为成员(member),不重复,访问元素
值被称为分值(score),必须为浮点数,排序顺序访问它的内部实现用的是一种叫做 「跳跃表」 的数据结构 (金字塔的结构)
常用命令:zadd zrem zrang zrangbyscore zcard(集合元素数量) zcount zrank zscore
zrevrank keyname member 返回member的排名,按分值从大到小排列
zrevrang keyname start end 返回给定排名范围的成员
zrangbyscore key max min 获取分值介于min max之间的所有成员
zremrangebyscore key start end 移除分值在范围之间的成员
zremrangebyrank key start end 移除排名在范围之间的成员
zinterstore (‘zset3’ , [ ‘zset1’ , ‘zset2’ ]) 交集 ,默认使用聚合函数sum
zunionstore (‘zset3’ , [ ‘zset1’ , ‘zset2’ ]) 并集 -
跳跃表
多层链表,上面每一层链表的节点个数大约是下面一层节点个数的一半。
为每个节点随机出一个层数,默认允许的最大层数是32.
优点:插入和删除操作只需修改相邻节点的指针,而平衡树可能引发树的调整。实现简单。 -
其他命令:
排序:sort
键的过期时间 expire key seconds ttl(距离过期秒数) persist key (移除过期时间)
二.持久化
-
持久化发生了什么|从内存到磁盘
(1).客户端向数据库发送写命令 (数据在客户端内存)
(2).数据库接收到写请求 (服务器内存)
(3).数据库调用API将数据写入磁盘(内核缓冲)
(4).操作系统将写缓冲区传输到磁盘控制器(磁盘缓存)
(5).磁盘控制器将数据写入实际的物理媒介(磁盘)
步骤3、4、5是 数据安全最重要的阶段
fsync:强制内核将缓冲区写入磁盘(消耗性能,建议每隔1s执行一次) -
两种持久化方式
(1).快照
保存某个时间点的数据状态,.rdb文件,经过压缩的二进制文件,可以还原成之前的数据状态。
生成RDB文件命令:save(阻塞进程)、bgsave(派生子进程执行,完成后向父进程发送信号)问题:redis是单线程,若持久化的同时,数据机构还在变化怎么办?
使用系统多进程COW(Copy On Write)|fork函数:
redis在持久化时会调用glibc的fork函数,基于当前进程复制一个子进程,共享内存和数据段。快照持久化完全交给子进程处理(不会修改现有的数据),父进程继续处理客户端请求。注意: 1. AOF文件比RDB文件更新频率更高,若开启了AOF,则优先使用AOF文件还原数据。 2. 执行bgsave时,服务器拒绝save/bgsave(防止竞争),延迟bgrewriteaof(性能) 3. 载入RDB文件时,服务器也会处于阻塞状态。 RDB文件结构: 最开头REDIS部分:标识文件是RDB文件 db_version: 版本号 databases: 0个或任意多个数据库 EOF: 正文内容结束 377 check_sum: 八个字符,校验和,以上四个部分内容进行计算得出的,检查RDB文件是否出错或损坏。
(2).Append Only File只追加文件
记录所有被执行的写命令。
先执行指令(参数校验、逻辑处理),再将日志存盘:由于AOF文件会比较大,先做指令校验避免写入无效指令;保证了指令的有效性