最近又复习了一下redis中比较重要的几个知识点,知识点多且碎,在这里做一个简单的总结,便于以后复习。
主流应用架构
我们都知道多数情况下redis是作为缓存应用来使用的,下面则显示出当前主流的应用架构(客户端、缓存、存储层).
对比缓存中间件 Memcache和Redis的区别
⭐️Memcache: 在代码层次上比较类似于Hash
- 支持简单的数据类型
- 不支持数据持久化存储
- 不支持主从
- 不支持分片
⭐️Redis
- 数据类型丰富
- 支持数据磁盘持久化存储(RDB、AOF)
- 支持主从
- 支持分片
我们知道,Redis是内存级数据库,它的QPS(Query Per Second)可以达到100000+,那它为啥那么快呢?
原因如下:
- 完全基于内存,绝大多数是存粹的内存操作
- 数据结构简单,对数据操作简单(如利用了Hash的查找为O(1)的特性)
- 采用单线程(在处理网络请求时是单线程),单线程也能处理高并发的需求,如果想要多核的话可以启动多个实例
- 使用多路I/O复用模型,非阻塞I/O
下面我们便开始聊一聊Redis的多路I/O复用模型,在聊这个之前,我们首先应该清楚一个概念:
FD 文件描述符(File Descripto):一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件元数据到文件本身的映射.
I/O模型有:1.传统的I/O阻塞模型 2.多路I/O复用模型(可以同时对多个fd的状态进行监控)
Redis采用的I/O多路复用函数: epoll/kqueue/evport/select
优先选择O(1)的I/O多路复用函数作为底层实现,以时间复杂度为O(n)的来保底,基于react设计监听I/O事件(监听多个fd)
简单聊一下Redis的数据类型
1.String k,v 最基本的类型,二进制安全(可存图片) [incr 可用作网站用户量统计]
2.Hash String元素组成的字典,适合存储对象。hmset lilei name "lilei" age 26 title "senior"
3.List列表 按照插入顺序排序 lpush rpush. 查询0-10的记录 orange mylist 0 10
4.Set String元素的无序集合,不重复。可以进行交、差、并等操作。sadd myset 111。smembers myset —>遍历所有元素
5.Sorted Set 通过分数为元素大小排序,不重复 zadd myzset 3 abc ; zrangebyscore myset 0 10
还有一些比较高级的如用于计数的HyperLogLog和用于支持存储地理位置的Geo
此外可以看一下<<Redis的设计与实现>>这本书中对于Redis底层数据类型基础的介绍,以上的所有对象都是基于更加底层的数据结构实现的
如何从海量数据里(2000w+)查询出某一个固定前缀的key
遇到这种问题时,应该首先问清楚数据量的范围规模,问清楚边界,再进行思考和回答
KEYS pattern : 查找所有符合给定模式pattern 的key, 例如 keys k1 查找所有以k1开头的key*
使用keys 对线上业务有什么影响?
keys指令会一次性返回所有匹配的key,键的数量太大会导致服务卡顿
为了解决这个问题 引入了SCAN cursor
基于游标cursor的迭代器,需要基于上一次游标延续之前的迭代过程,以0作为游标作为一次新的迭代,直到返回游标0完成一次遍历。
不保证每次执行都返回某个给定数量的元素,支持模糊查询。
-
一次返回的数量不可控,只能是大概率符合count参数
Scan 0 match k1* count 10
可能获取的数据数量不是10条,可能获得重复元素,要在程序中去重
如何通过Redis实现分布式锁?
要想实现分布式锁,我们需要考虑到以下需求:
- 互斥性
- 安全性
- 注意死锁问题
- 容错
可以利用Redis如下命令的特性实现一个分布式锁
SETNX key value :如果key不存在,创建并且赋值(用key作为锁)
但这样是占有了一个锁,如何释放掉它呢?也就是说如何解决SETNX长期有效的问题
EXPIRE key seconds :设置过期时间删除模拟锁的释放
下面看一个伪代码的实现
long status = redisService.setnx(key,"1");
//注释1
if(status==1){
redisService.expire(key,expire);
doSomething();//......
}
上面的代码看上去似乎没有什么问题,但是仔细想想,如果执行到注释1的地方的时候,redis服务器发生了宕机怎么办? 所以我们要保证setnx操作和expire的设置是原子的
所以redis引入了以下命令
set locktarget 12345(可以写线程的标识) ex 10 nx //该操作是个原子操作,成功返回OK,失败返回nil
伪代码实现:
String result = redisService.set(lockkey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expiretime);
if("OK".equals(result)){
doSomething();//....
}
大量的key同时过期怎么处理?
key集中过期,清除大量的key很耗时,会出现短暂的卡顿现象
解决方案: 在设置key的过期时间的时候,给每个key加上随机的值
如何用Redis做异步队列?
使用list做异步队列, rpush生产消息,lpop消费消息
缺点:没有等待队列里有值就进行直接消费
弥补:可以在应用层引入sleep机制去调用lpop重试
也可以用以下命令
BLPOP key[key...] timeout : 阻塞直到队列有消息或者超时
缺点:只能供一个消费者消费
Pub/Sub 主题订阅模式
这里有三个客户端连接 分别为cli-1、cli-2、cli-3
cli1:6379-> subscribe myTopic
cli2:6379-> subscribe myTopic
cli3:6379-> publish myTopic "Hello"
然后cli1 和 cli3便会接收到hello消息
这里请注意: 消息的发布是无状态的,无法保证可达
Redis如何做持久化?
1.RDB(快照)持久化:保存某个时间点的全量数据快照
在redis.conf 中可以配置 RDB持久化方式的持久化策略
- save 900 1 //在900s内进行一次写操作触发持久化
- save 300 10
- Save 60 10000
stop-writes-on-bgsave-error yes
当备份进程出错误时,主进程停止接受新的写入操作(保证持久化的数据一致性问题)
rdbcompression no
建议设置为no,关闭压缩,降低cpu损耗(因为Redis本身就是CPU密集型)
手动持久化的命令
1.SAVE : 阻塞Redis的服务器进程直到RDB文件被创建完毕
2.BGSAVE: fork出一个子进程来创建RDB文件,不阻塞服务器进程
自动触发RDB持久化的方式
- 根据redis.conf配置的save m n 定时触发(用的bgsave)
- 主从复制时,主节点自动触发
- 执行debug reload
- 执行shutdown 且没有开启ROF持久化
什么是 Copy-On-Write 写时复制?
如果有多个调用者同时获取相同的资源的时候,他们会获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容的时候,系统才会真正复制一份专有副本给调用者,而其他调用者见到的最初资源保持不变。
当redis做持久化时,redis会fork一个子进程,将数据写入磁盘中的一个临时的rdb文件中,当子进程完成写临时文件之后,将原来的rdb替换掉,这样的好处是可以实现copy-on-write,子进程继续可以接受其他请求,确保了redis性能。
缺点:内存数据的全量同步,当数据量大的时候会由于I/O而严重影响性能,可能会因为redis挂掉而丢失从当前至最近一次快照期间的数据。
2.AOF(Append-Only-File)持久化:保存写状态
- 记录下除了查询以外所有变更数据库状态的指令
- 以append的形式追加保存到AOF文件中(增量)
AOF的持久化默认是关闭的. vim redis.conf
appendonlyno. —>修改为 appendonly yes 生效
appendfilename "append only.aof"
appendfsync:可以指定AOF写入方式 :
1.always 2.everysec 3.no
AOF日志重写 bgrewrite aof
日志重写解决AOF文件大小不断增大的问题,原理如下:
- 调用fork(),创建一个子进程
- 子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件
- 主进程持续把新的变动同时写入内存和原来的AOF里
- 主进程获取子进程的重写AOF的完成信号,往新的AOF同步增量变动
- 使用新的AOF文件替换掉旧的AOF文件
Redis数据的恢复
RDB和AOF文件共存情况下的恢复流程
RDB-AOF混合持久化的方式
BGSAVE做镜像全量持久化,AOF做增量持久化
总结RDB和AOF的优缺点
- RDB优点:全量数据快照,文件小,恢复快
- RDB缺点:无法保存最近一次快照后的数据
- AOF优点:可读性高,适合保存增量数据,数据不易丢失
- AOF缺点:文件体积大,恢复时间长
Redis主从复制同步
1.全同步过程
- Slave发送sync命令到master
- master启动一个后台进程,将redis中的数据快照保存到文件中
- master将保存快照期间收到的命令缓存起来
- master完成写文件操作后,将该文件发送给salve
- 使用新的AOF文件替换掉旧的AOF文件
- master将这期间收到的写命令发送给salve,进行回放
2.增量同步的过程
- master接受用户的操作指令,判断是否需要传播到salve
- 将操作记录追加到aof文件
- 将操作传播到其他slave:1.对齐主从库 2.往响应缓存中写入指令
- 将缓存中的数据发送给slave
主从模式不具备高可用性,当master挂掉以后,slave将无法对外提供写入操作,为解决该问题,引入redis sentinel(哨兵)解决主从同步宕机后的主从切换问题
- 监控:检查主从服务器是否运行正常
- 提醒:通过API向管理员或者其他应用程序发送故障通知
- 自动故障转移:主从切换
流言协议 Gossip 在杂乱无章中寻求一致
每个节点都随机与对方通信,最终所有节点的状态达成一致。
种子节点定期随机向其他节点发送节点列表以及需要传播的信息
不保证信息一定会传递给所有的节点,但最终会趋于一致性。
关于redis集群的更多总结我会放到下一篇文章里,今天的总结就到这里了!