• 分布式缓存


    引用:https://www.cnblogs.com/boothsun/p/8601474.html

    一、redis

    redis是nosql,数据存于内存,单线程,用了多路复用I/O,1秒可处理10w的并发

    1、redis支持的数据类型:

    string:二进制类型,一个键最大能存储512MB;

    list:是简单的字符串列表,底层是由双向链表实现,可以将元素插入到列表的头部或尾部;

    • 可以利用lists来实现一个消息队列,而且可以利用链表来确保先后顺序。
    • 利用LRANGE还可以很方便的实现分页功能。

    set :是无序集合,记录一些不能重复的数据,最大可以包含(2 的 32 次方-1)个元素。set 的是通过 hash table 实现的,所以添加,删除,查找的复杂度都是 O(1)

             set包含集合的取并集(union),交集(intersection) ,差集(difference),可实现 SNS 中的好友推荐和 blog 的 tag 功能。

    • set查询该用户名是否被注册,效率极高;
    • 投票系统,比如一天用户只能投票一次;

    sorted sets:是有序集合(sorted sets),其中每个元素都关联一个序号(score),这便是排序的依据。

    • 对博客按照顶贴进行排序

    hash:存的是字符串和字符串值之间的映射,比如一个用户要存储其全名、姓氏、年龄等等,就很适合使用哈希。

    2、redis数据持久化

    一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB文件持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志追加的方式写入文件)。

    RDB

    RDB持久化既可以通过saveBGSAVE命令进行手动持久化,也可以根据服务器配置选项定期执行,其实定期执行的进程也是执行的BGSAVE命令。

    SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何客户端命令请求,所以一般不推荐使用SAVE命令。

    BGSAVE不会直接阻塞服务器进程,相反它会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理请求。处理流程图如下:

    image.png-31.5kB

    AOF

    AOF,英文是Append Only File,即只允许追加不允许改写的文件。

    AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。在服务器重启时,Redis服务器会载入和执行AOF文件中保存的命令来还原服务器关闭之前的数据库状态。

    image.png-25.4kB

    AOF优点:

    默认的AOF持久化策略是每秒钟fsync一次,即使redis故障,也只会丢失最近1秒钟的数据;

    如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,edis提供了redis-check-aof工具,可以用来进行日志修复;

    因为采用了追加方式,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即简单地读取了当前数据库中该键对应的值,然后使用一条set指令将这个值写入到新的AOF文件中。

    数据恢复:某同学在操作redis时,不小心执行了FLUSHALL,导致redis内存中的数据全部被清空,如果AOF文件没有被重写,可以将最后一条指令删除,重新执行进行数据恢复;

    AOF缺点:

    同样数据规模的情况下,AOF文件要比RDB文件的体积大。而且,AOF方式的恢复速度也要慢于RDB方式。

    3、redis主从同步

    像MySQL一样,Redis是支持主从同步的,而且也支持一主多从以及多级从结构。

    主从结构,一是为了纯粹的冗余备份,二是为了提升读性能,比如很消耗性能的SORT就可以用从服务器承担。

    主从架构中,就可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样就可以提高主服务器的处理性能。

    全量同步:

    1. 从服务器向主服务器发送 PSYNC命令。
    2. 收到PSYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
    3. 当主服务器的BGSAVE命令执行完毕时,主服务器会将RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
    4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器当前所处的状态。

    image.png-22.7kB

    另外,要说的一点是,即使有多个从服务器同时发来SYNC指令,主服务器也只会执行一次BGSAVE,然后把持久化好的RDB文件发给多个下游。

    因为这个过程对从服务器和主服务器都是一个非常消耗资源的过程:

    1. 主服务器需要执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘I/O资源。
    2. 主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的事件产生影响。
    3. 接收到RDB文件的从服务器需要载入主服务器发过来的RDB文件,并且在载入期间,从服务器会因为阻塞而无法处理命令请求。

    增量同步:

      Redis增量复制是指从服务器初始化后,主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

    Redis主从同步策略:

      主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求重新进行全量同步。

    4、哨兵模式(Sentinel)

    Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

    哨兵进程的工作方式:

    1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令 
    2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。 
    3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。 
    4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 
    5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 
    6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 
    7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除,若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除

     5、Redis的内存淘汰策略

    Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

    noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

    allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。

    allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

    volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

    volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

    volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

    6、key过期清除策略

    惰性过期(类比懒加载,这是懒过期):只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

    定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

    7、其他

    使用redis如何设计分布式锁?使用zk可以吗?如何实现啊?这两种哪个效率更高啊??

    zk使用临时节点,使用完毕之后要主动删除临时节点,Zookeeper分布锁,首先创建加锁标志文件,如果需要等待其他锁,则添加监听后等待通知或者超时,当有锁释放,无须争抢,按照节点顺序,依次通知使用者。

    Redis分布式锁,必须使用者自己间隔时间轮询去尝试加锁,当锁被释放后,存在多线程去争抢锁,并且可能每次间隔时间去尝试锁的时候,都不成功,对性能浪费很大。

    二、Memcache

    Memcache可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS,适用于最大程度扛量;

    数据存在内存中,只支持简单的key/value数据结构,无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失;

    三、缓存的问题

    缓存穿透:

     缓存穿透指的是查询一个根本不存在的数据,缓存层不命中,又去查存储层,又不命中。但如果有大量这种查询不存在的数据的请求过来,DB可能挂掉,若是恶意攻击,就是漏洞。

    解决方案:

    • 布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
    • 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

    缓存雪崩

    缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

    解决方案:

    • 使用互斥锁(mutex key):就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
    • 在原有的失效时间基础上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低。

  • 相关阅读:
    设计模式之适配器模式(Decorator)
    可复用面向对象软件的基础
    dpkg命令的用法
    UML类图几种关系的总结
    Java多线程并发编程之原子变量与非阻塞同步机制
    Java内部类总结
    多线程并发编程之构建自定义同步工具
    多线程并发编程之显示锁ReentrantLock和读写锁
    多线程并发编程之变量
    汇编语言学习系列 冒泡排序实现
  • 原文地址:https://www.cnblogs.com/tilamisu007/p/9640607.html
Copyright © 2020-2023  润新知