使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。除了缓存之外,Redis 也经常用来做分布式锁,甚至是消息队列。
1.特点:
- 高性能:内存操作速度快
- 高并发:相对于普通数据库能支持更高的并发度
- 丰富的数据类型:相对于memcached所支持的数据类型更多
- 持久化:有rdb,aof这些持久化机制
- 部分支持事务:使用Lua脚本可以让一组命令原子执行、
- 支持数据备份:master-slave模式数据备份
2.性能高的原因
- 纯内存操作
- 单线程操作,避免频繁上下文切换,(操作简单,不需要多线程加持)
- 采用非阻塞I/O多路复用机制,事件被多路复用程序放入队列中,然后事件分派器依次去队列中取,转发到不同的事件处理器中。 redis还提供了select、epoll、evport、kqueue等多路复用函数库。reacter模式。
3.数据类型
- string 字符串或者数字,复杂计数功能缓存 get set strlen exist del
- hash 结构化对象,操作其中某个字段,单点登录可以用其保存用户信息,以cookieId作为key,设置缓存过期时间,模拟出类似session的效果 hset和hget 先压缩表,元素过多时hashtable
- list 消息队列功能,使用lrange命令,做基于redis的分页功能 lpush lpop rpush rpop lrange lindex blpop brpop
- set 集合,全局去重,利用交并补计算共同喜好、全部喜好、独有喜好等。 sadd srem
- zset 多了一个权重score,排行榜,取top N,延时任务,范围查找等。 底层先使用压缩表,当元素过多时跳表实现
- bitmap 保存连续二进制数,节省空间,非核心了。。
- Streams 功能强大,支持多播,可持久化的消息队列
渐进式Rehash
当哈希表存储巨大数量键值对,如果一次rehash,可能宕机
步骤:
- 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
- 维持索引计数器比那两rehashidx,设为0,表示开始
- 每次对字典增删查改,ht[0]的rehashidx索引上的所有键值对rehash到ht[1],将rehashidx值+1。
- 当全部键值对移动完成,rehashidx设置为-1,表示操作完成
这样避免了集中rehash大计算量造成问题,将计算均摊到每个字典增删查改操作。
4.应用场景
- 排行榜 zset
- 计数器 string类型的原子自增操作
- 好友关系 利用集合交并补
- 简单消息队列 list自带机制
- session共享
- 分布式锁
5.数据过期策略和淘汰机制
5.1.过期策略
过期字典(hash表),key为数据库的键,value是long long类型整数,保存了key指向的数据库键的过期时间。
- 惰性删除:当访问key才判断是否过期,节省CPU对内存不友好。
- 定期删除:每100ms抽取部分数据进行删除,漏掉的数据在用到的时候惰性删除,折中内存和CPU。
过期的贪心策略:
- 从过期字典中随机选择20个key;
- 删除这20个key中已过期的key;
- 如果已过期key的比例超过25%,则重复步骤1。
5.2.内存淘汰机制
如果数据一直没有被选择清除掉,而且也没有访问的惰性清除,将会一直占用内存,所以需要数据淘汰策略防止内存不够用。
在redis.conf中的maxmemory-policy中进行配置
- noeviction 内存不够报错
- allkeys-lru 移除最近最少使用,推荐
- allkeys-random 随机删除
- volatile-lru 内存不足时在设置了过期时间的键空间移除最近最少使用,一般又当缓存,又当持久化存储的时候用。
- volatile-random …随机
- volatile-ttl 有更早过期时间的优先移除
如果没有设置expire,那么在volatile的表现和noeviciton一致。
传统的lru和lfu使用链表实现,按使用时间排序,淘汰最久的。redis则不维护队列,而是从key中随机选择n个,然后淘汰最久的,为了提高效率。
6.缓存与数据库
6.1.缓存穿透
缓存和数据库中都没有的数据,用户不断请求,导致数据库压力过大,一半是攻击,解法:
- 缓存空数据,设置一个短的过期时间。
- 布隆过滤器,使用hashmap判断该key是否存在,不存在的数据直接拦截。
6.2.缓存击穿
缓存中没有,数据库中有点数据,并发量太大情况下,数据库压力过大,解法:
6.3.缓存雪崩
缓存同一时间大量失效,使得请求都堆积到数据库中,导致数据库崩掉,解法:
- 使用集群缓存,保证缓存服务高可用
- 缓存失效时间需要随机,避免同一时间集体失效
- 设置热点数据不过期
- 加锁,防止并发过多
- 限流算法
6.4.保证缓存和数据一致性
如果对数据有强一致性要求,不能放缓存。缓存中只能保证最终一致性,一般先更新数据库,再删除缓存。
保证这个顺序的原因是如果先删缓存那么如果有请求过来又会把旧值缓存。
如果一定要先删缓存,那么需要使用延时双删,先缓存,再数据库,过段时间再删缓存。
6.5为什么不频繁更新缓存
- 有时候代价比较高,因为数据库取出的数据可能会涉及复杂运算,更新代价高
- 冷数据访问少,频繁更新开销大
- 两个线程都更新数据库,再更新缓存,会出现顺序变化的不一致,加锁控制等
7.Redis的线程
7.1.Redis6.0之后为何引入多线程
Redis的性能瓶颈主要来自于内存和网络而不是操作,引入目的是提高网络IO性能,即多线程只在网络耗时上操作,执行命令仍然采用单线程。
7.2.Redis的Reactor模式
异步IO模式,当IO完成之后,回调对应的函数进行处理。
Redis基于Reactor模式开发了自己的网络事件处理器,被称为文件事件处理器。
- 多个socket (客户端连接)
- IO多路复用程序 (支持多个客户端连接的关键)
- 文件事件分派器 (将socket关联到相应的事件处理器)
- 事件处理器 (连接应答处理器、命令请求处理器、命令回复处理器)
实现一个redis线程处理多个IO流的效果。
8.持久化机制
快照和只追加文件
8.1.AOF
增量备份,记录每次对服务器写的操作,服务器重启时会重新执行这些命令恢复原始数据。
更安全,效率更低。实时性较好,已成为主流方案。
默认不开启,如果开启要修改配置文件
appendonly yes
每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘的AOF文件。
三种策略控制系统冲洗AOF的频率
特点
- 优点
- 更新频率更高,实时性较好,即使故障最多损失1s数据。
- 以文本方式写,具有可读性
- 可以进行rewrite操作,将操作合并,压缩文件
- 缺点
8.2.RDB
定时快照存储,在指定时间间隔对数据进行快照存储。
在指定时间间隔(也可以手动触发)将内存中数据集快照写入磁盘,创建子进程写入临时文件dump.rdb,父进程可以做别的事,生成完成之后覆盖原来的快照
- 优点:
- RDB生成紧凑压缩的二进制文件,体积小,恢复速度快
- 缺点:
- 每次运行要fork操作创建子进程,开销大
- 安全性较低,如果间隔时间内挂掉,会造成数据丢失,适合在数据要求不严谨的情况下使用。
8.3.RDB-AOF混合持久化
在执行AOF重写操作时,按如下原则处理
- 根据数据库当前状态生成的RDB数据,写入AOF中
- 对于重写之后执行的Redis命令,以协议文本方式追加到AOF末尾
结合两种方式的优点,RDB的快速恢复能力和AOF的丢失数据窗口。
8.事务实现
8.1.特性
- 原子性 Redis不完全满足原子性,事务队列中的命令要么都执行,要么都不执行,但是redis没有回滚机制,事务出错仍然可以执行,为了保证效率的牺牲
- 持久性 提供了两种持久化机制,但是仍然可能造成数据丢失
- 隔离性 Redis由单线程模式执行,串行可以保证隔离性
- 一致性 Redis不具有一致性,因为其他三个特性也没法完全保证
8.2.实现命令
- multi 开启事务模式
- watch 监视key,如果事务执行之前某个key被其他命令用了,就取消事务
- Exec 执行
- Discard 取消事务
8.3.实现步骤
- 开启事务 multi,打开客户端的REDIS_MULTI选项,进入事务状态
- 命令入队 在事务状态下会将操作放入队列,而不是立即发送服务端执行
- 执行事务 exec 将队列中命令按照先进先出的方式执行,并将结果保存到回复队列中,当所有命令执行完成之后将回复队列返回给客户
9.和memcached比较
共同点:两者都是基于内存的数据库,一般都是当作缓存使用。
区别:
| Redis | Memcached |
|---|
| 数据类型 | 丰富的数据类型 list set等 | 只支持key-value |
| 持久化 | 支持将硬盘保存到磁盘 | 将数据全部存储在内存中 |
| 内存用尽 | 内存使用完可以转移到磁盘或者淘汰 | 内存使用完会报错 |
| 线程 | 单线程 | 多线程 |
| 集群 | 支持cluster模式 | 没有原生集群模式,依靠客户端往集群中分片写入数据 |
memcached在线程方面较Redis有优势,所以不考虑持久化和复杂数据结构的情况下,可以使用memcached。
10.高可用方案
10.1.主从模式
一个master拥有多个slave,主从复制不会阻塞,master可以继续处理客户端命令。
-
优点:读写分离,分载主节点压力
-
缺点:
- 存在master宕机时数据未同步造成的不一致
- 不具备自动容错和恢复机制,请求失败需要手动回复
- 不能在线扩容
10.2.哨兵模式
基于主从模式,引入哨兵来监控和自动处理故障。监控master和slave是否正常运行(ping),当多个哨兵发现主节点没有反应的时候,自动选出slave作为master。
下线模式
- 主观下线:一个哨兵发现节点没有回应。
- 客观下线:发现主观下线后,向其他哨兵确认后才是客观下线。
优缺点
- 优点:可以自动切换节点为master
- 缺点:仍然无法自动扩容,数据不一致
10.3.集群模式
主从模式和哨兵模式下,每个redis节点都存储相同数据,耗费资源。
cluster下将数据分成一万多个槽,计算公式为slot=CRC16(key)&16383,分配保存到互为主从的节点上。每个节点都是互相连接的,会保存自己与其他节点的信息。数据先写到主节点,再同步到从节点,读取数据时,输入key不在当前节点上,则指向正确节点。
操作步骤:
- 操作到达时,使用算法获得数据所在的槽
- 跳转到槽所在节点,进行操作
- 当半数主节点和某个节点通信时发现其宕机,将其从从节点转为主节点
- 优点
- 无中心架构,多主多从
- 支持动态扩容
- 客户端只需要访问一个节点,效率高
- 自动恢复,采用选举机制切换master
- 缺点
- 批量操作支持有限
- 事务操作支持有限
- 不能将大键值对象映射到不同节点
- 不支持多数据库空间,单机下有16个,集群只能DB0
- 复制结构只支持一层,从节点只能复制主节点,不可嵌套
11.主从复制
11.1.原理
- 全量复制,主节点生成rdb文件,发送给从节点,从节点删除数据,载入rdb文件
- 增量复制,维护每次执行的偏移量,通过主节点内部的缓冲队列,向从节点复制。
三种只能使用全量复制的情况:
11.2.步骤
- 从服务器收到slaveof命令
- 从服务器向主服务器发起SYNC或者PSYNC命令
- 主服务器执行BGSAVE命令,生成RDB文件,并用缓存区记录从现在所有的写命令
- RDB文件生成完成后,主服务器将其发送给从服务器
- 从服务器载入RDB文件,将数据库状态同步更新为主服务器执行BGSAVE命令时的状态
- 主服务器将缓冲区的所有写命令发送给从服务器,从服务器将执行这些写命令,数据库状态同步为主服务器的最新状态
12.分布式锁实现
setnx命令返回整数值,为1成功,0失败。
一般不能直接用其实现分布式锁,因为可能死锁,需要设定自动过期时间,而过期时间和设置命令不是原子的,redis改进了set命令,增加了nx选项,启用该选项的效果就会和setnx一样了。
setnx key value
setnx key value expire key seconds
set key value nx ex seconds
解锁
del key
加锁需要给锁设置标识,进程只能释放自己的锁,结合lua脚本编排命令
# 加锁
set key random-value nx ex seconds
# 解锁
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1])
else return 0
end