redis入门之路

为什么要用缓存?
    常见的sql数据库(如mysql,oracle等)的数据是存在磁盘中的,虽然数据库本身会有缓存技术来减少数据库IO的压力,但是由于数据库的缓存一般是针对于查询内容,并且粒度较小,一般只有表中数据没变化时,数据库中的缓存才会发产生作用。这并不能减轻数据库增删改的IO压力,因此缓存技术应运而生,该技术实现了对热点数据的高速缓存,缓解数据库压力。
 
数据库本身缓存技术?(mysql/oracle)
    
 
主流应用架构:
    
什么样的数据适合缓存?
 
 
什么是缓存击穿?
    使用不存在的key频繁大量的进行高并发查询,导致缓存无法命中,每次请求都要击穿到后端数据库查询,最终可能导致数据库服务崩溃。
    可以将空值缓存,并且设置较短的过期时间以应对恶意攻击,设置较短的时间能避免缓存空间的浪费。
    避免恶意攻击方猜到我们使用这种策略,进而选择使用不同的key来攻击,可以进行数据拦截校验,如果key不符合我们的规则,直接返回,能拦截一部分的恶意请求。
 
什么是缓存并发?
    当缓存失效时,高并发场景下,可能有大量请求同时访问同一个key,并且要写入数据到缓存,会增加应用和数据库的压力,违背了缓存的设计初衷,可能导致应用崩溃或者数据库崩溃。
    1.加锁(分布式锁/本地锁):
        使用分布式锁(本地锁),保证同一个key只有一个线程去访问数据库,其他线程等待数据写入缓存后,从缓存读取数据即可。
                                图示双重检查锁
 
    2.不设置缓存过期时间,由后端定时任务控制刷新缓存数据,这样就不需要担心缓存过期,但要考虑数据量大的话,分批刷新缓存数据
 
什么是缓存雪崩?
    缓存雪崩,是指在同一时刻,缓存集体失效,会导致数据库在这个时刻承受巨大压力。
    
什么时候使用redis?什么时候使用memcache?
    主要有以下方面:(详见链接)
        内存结构
        持久化
        高可用
        内存分配
 
为什么redis这么快?
    redis效率很高,官方给出的数据是100000+QPS(每秒查询率)
    1.redis完全基于内存
    2.redis是使用单进程单线程的(k,v)数据库,数据存储在内存中,不受磁盘I/O制约
    3.数据结构简单,操作也简单,redis是no-sql数据库,不使用表,才用key-value键值对存储,存取效率高
    4.redis使用多路I/O复用模型,为非阻塞I/O,redis使用的I/O多路复用函数是:
epoll/kqueue/evport/select
    选用策略:
    因地制宜,优先选择时间复杂度为 O(1) 的 I/O 多路复用函数作为底层实现。
    由于 Select 要遍历每一个 IO,所以其时间复杂度为 O(n),通常被作为保底方案。     基于 React 设计模式监听 I/O 事件。
 
redis数据类型?
    1.String类型:最大512M,二进制安全(可包含任何二进制数据,包括jpg等)
    重复写入,会覆盖之前数据
    
    2.Hash类型:String元素组成的字典,用于储存对象
    
    3.List类型:列表,按照String插入顺序,后入先出,具有栈的特性(最新消息排行)
    
    4.Set类型,String元素组成,无序集合,通过哈希表实现(增删改查时间复杂度O(1)),不允许重复
 
    5.Sorted Set:通过分数来为集合排序(从小到大),不允许重复
 
更高级的redis数据类型:
    用于计数的HyperLogLog,用于支持存储地理位置信息的Geo
 
从海量key中查询出某一个固定前缀的key?
    1.使用keys[pattern]:查找符合条件的所有key,一次性返回,可能造成redis卡顿,同时也会大量消耗内存
    2.使用scan cursor [pattern] [count]
        cursor:游标
        pattern:查询条件
        count:返回的条数
 
分布式锁?
    控制分布式系统之间共同访问共享资源的一种锁的实现。如果一个系统,或者不同系统之间共享某个资源时,要互斥,确保数据的一致性
    分布式锁要解决的问题:
        1.互斥性:任意时刻,只能有一个客户端获取锁
        2.安全性:锁只能被持有的客户端删除
        3.死锁:当客户端获取锁以后宕机,由于没有释放锁,会导致其他客户端也不能持有锁,需要解决来避免死锁
        4.容错:当某个redis节点宕机的时候,客户端依然可以持有和释放锁。
 
如何通过redis实现分布式锁?
    1.通过setnx来实现:key不存在,就创建并且赋值,返回1,否则返回0
    setnx操作是原子性的
    setnx是长久存在的,所以当持有锁的客户端,由于某些问题没有释放锁的时候,会导致其他客户端一直不能正常持有锁。
    可以通过expire 指令来设置锁的过期时间
    程序:
    问题在于,这两步操作并非原子性操作,所以当在设置过期时间之前,程序就出现故障时,锁还是无法释放
    2.从redis的2.6.12版本以后,可以通过set指令来完成
ex seconds    键过期时间为seconds秒
px milliseconds  键过期时间为milliseconds毫秒
nx    只有键不存在时才进行操作
xx    只有键存在时才进行操作
设置成功返回ok,失败返回nil。
代码示例:
RedisService redisService = SpringUtils.getBean(RedisService.class);
String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
    if("OK".equals(result)){   
        doOcuppiredWork(); 
    }
 
如何实现异步队列?
    1.使用redis中的list作为队列
    rpush生产消息,lpop消费消息
缺点:
    lpop不会等待生产队列中有消息才会去消费,而是直接消费
解决:
    通过在应用层引入sleep机制去调用lpop重试
    2.使用 BLPOP key [key…] timeout
缺点:生产后只能给单一消费者消费
    3.pub/sub:主题订阅模式
    订阅者可以订阅任意数量的频道
缺点:消息发布是无状态的,无法确保消息的送达。比如一个消费者在消息发布时下线,那么重新上线以后无法接收到消息。如果要求较高,最好使用专业的消息队列,如kafka,active mq等~
 
redis持久化
    1.什么是持久化?
        数据持久储存,redis的数据一般存于内存中,如果内存断电,则数据也会丢失,redis有持久化机制来解决这一问题。
    2.redis如何持久化?
        RDB和AOF。
        RDB(快照):会在某个特定的间隔,保存那个时间点的全量数据快照。
        redis RDB配置:
    a.RDB的创建和载入:
        save:save指令会阻塞redis进程,知道整个rdb文件创建完成,因此很少使用
        bgsave:fork一个子进程来进行rdb文件的写入。子进程完成创建会想父进程发送信号,父进程在接收客户端请求的过程中,在一定的时间间隔会通过轮询来接收子进程的信号
        可以通过lastsave指令来查看bgsave的执行是否成功,lastsave会返回最后一次bgsave执行成功的时间
    b.自动化触发rdb持久化
        方式:
            根据redis.conf配置里面的save m n定时触发(实际上使用的是bgsave)
            主从复制时,主节点自动触发
            执行debug reload
            执行shutdown 且没有开启AOF
    c.bgsave的执行原理
启动:
    检查是否有子进程正在执行AOP或者RDB持久化任务,如果是,直接返回false
    调用redis源码中的rdbbackground方法,方法中执行fork(),产生子进程,执行rdb操作
    fork()中的copy-on-write
    fork() 在 Linux 中创建子进程采用 Copy-On-Write(写时拷贝技术),即如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储)。 
    他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给调用者,而其他调用者所见到的最初的资源仍然保持不变。
缺点:
    全量同步,数据量大的时候,可能会由于I/O严重影响性能
    可能会丢失从当前到最近一次快照期间的数据
 
AOF持久化:保存写状态
    AOF持久化是通过保存redis写状态来记录数据库的,保存的是redis出查询外的变更数据库的指令
    以增量的形式,保存在aof文件中
 
开启AOF持久化?
    1.将redis.conf中的appendonly修改为yes
    2.修改appendfsync属性,有三个值:
        always:即时写入
        everysec:每隔一秒写入一次
        no:由操作系统决定,一般来说,考虑效率问题,操作系统会等待缓存区满后才写入
 
日志重写:解决AOF文件不断增大的问题:
    例如,计数器递增100次,使用rdb只需要存储最终结果100,而aof的方式会存储100条操作记录,事实上恢复数据,只需要一条指令即可,实际上,这100条指令可以精简为1条
    redis支持这样的功能,在不中断服务的情况下,fork()子进程,重写AOF文件,同样用到了c-o-w(写时拷贝)
重写过程:
    调用fork(),创建一个子进程
    子进程把新的AOF文件写到一个临时文件中,不依赖原有的AOF文件
    主进程持续将新的变动同时写到内存和原AOF文件
    主进程获取子进程重写AOF完成信号,往新的AOF文件增量变动
    用新的AOF文件替换旧的
    
两种方式优缺点比较:
    RDB优点:数据量相对较小,恢复快
    RDB缺点:无法报错最近一次快照之后的数据
    AOF优点:可读性高,适合保存增量数据,不易丢失
    AOF缺点:文件较大,恢复慢
 
RDB-AOF混合的持久化方式:
    redis 4.0以后,推出了这种持久化方式,RDB做全量备份,AOF做增量备份,并且作为默认方式使用。
    在RDB-AOF方式下,持久化策略是,先将缓存中数据以RDB的方式写入文件,再将增量数据以AOF的方式追加在RDB文件后面,下一次RDB持久化的时候将AOF数据重新写入。
 
 
 
posted @ 2020-01-02 15:29  星辰河流  阅读(307)  评论(0编辑  收藏  举报