美文网首页Redis
这是一份Redis学习总结,请查收

这是一份Redis学习总结,请查收

作者: lhsjohn | 来源:发表于2019-06-13 17:34 被阅读66次

    最近又复习了一下redis中比较重要的几个知识点,知识点多且碎,在这里做一个简单的总结,便于以后复习。

    主流应用架构

    我们都知道多数情况下redis是作为缓存应用来使用的,下面则显示出当前主流的应用架构(客户端、缓存、存储层).


    主流应用架构.png

    对比缓存中间件 Memcache和Redis的区别

    ⭐️Memcache: 在代码层次上比较类似于Hash
    • 支持简单的数据类型
    • 不支持数据持久化存储
    • 不支持主从
    • 不支持分片
    ⭐️Redis
    • 数据类型丰富
    • 支持数据磁盘持久化存储(RDB、AOF)
    • 支持主从
    • 支持分片

    我们知道,Redis是内存级数据库,它的QPS(Query Per Second)可以达到100000+,那它为啥那么快呢?

    原因如下:

    1. 完全基于内存,绝大多数是存粹的内存操作
    2. 数据结构简单,对数据操作简单(如利用了Hash的查找为O(1)的特性)
    3. 采用单线程(在处理网络请求时是单线程),单线程也能处理高并发的需求,如果想要多核的话可以启动多个实例
    4. 使用多路I/O复用模型,非阻塞I/O

    下面我们便开始聊一聊Redis的多路I/O复用模型,在聊这个之前,我们首先应该清楚一个概念:

    FD 文件描述符(File Descripto):一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件元数据到文件本身的映射.

    I/O模型有:1.传统的I/O阻塞模型 2.多路I/O复用模型(可以同时对多个fd的状态进行监控)

    IO多路复用.png

    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 主题订阅模式

    主题订阅模式.png

    这里有三个客户端连接 分别为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文件大小不断增大的问题,原理如下:

    1. 调用fork(),创建一个子进程
    2. 子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件
    3. 主进程持续把新的变动同时写入内存和原来的AOF里
    4. 主进程获取子进程的重写AOF的完成信号,往新的AOF同步增量变动
    5. 使用新的AOF文件替换掉旧的AOF文件

    Redis数据的恢复

    RDB和AOF文件共存情况下的恢复流程
    Redis数据的恢复流程.png

    RDB-AOF混合持久化的方式

    BGSAVE做镜像全量持久化,AOF做增量持久化

    总结RDB和AOF的优缺点

    • RDB优点:全量数据快照,文件小,恢复快
    • RDB缺点:无法保存最近一次快照后的数据
    • AOF优点:可读性高,适合保存增量数据,数据不易丢失
    • AOF缺点:文件体积大,恢复时间长

    Redis主从复制同步

    1.全同步过程
    1. Slave发送sync命令到master
    2. master启动一个后台进程,将redis中的数据快照保存到文件中
    3. master将保存快照期间收到的命令缓存起来
    4. master完成写文件操作后,将该文件发送给salve
    5. 使用新的AOF文件替换掉旧的AOF文件
    6. master将这期间收到的写命令发送给salve,进行回放
    2.增量同步的过程
    1. master接受用户的操作指令,判断是否需要传播到salve
    2. 将操作记录追加到aof文件
    3. 将操作传播到其他slave:1.对齐主从库 2.往响应缓存中写入指令
    4. 将缓存中的数据发送给slave

    主从模式不具备高可用性,当master挂掉以后,slave将无法对外提供写入操作,为解决该问题,引入redis sentinel(哨兵)解决主从同步宕机后的主从切换问题

    • 监控:检查主从服务器是否运行正常
    • 提醒:通过API向管理员或者其他应用程序发送故障通知
    • 自动故障转移:主从切换

    流言协议 Gossip 在杂乱无章中寻求一致

    每个节点都随机与对方通信,最终所有节点的状态达成一致。

    种子节点定期随机向其他节点发送节点列表以及需要传播的信息

    不保证信息一定会传递给所有的节点,但最终会趋于一致性。

    关于redis集群的更多总结我会放到下一篇文章里,今天的总结就到这里了!

    lhsjohn 转载请注明出处 谢谢!

    相关文章

      网友评论

        本文标题:这是一份Redis学习总结,请查收

        本文链接:https://www.haomeiwen.com/subject/lcxefctx.html