Redis学习笔记

  Redis是什么?

          Redis是C语言开发的一个开源的高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种NoSQL(not-only sql,泛指非关系型数据库)的数据库。

        Redis作为一个内存数据库,有如下优点:

        1、性能优秀,数据在内存中,读写速度非常快,支持并发10W QPS;

        2、单进程单线程,是线程安全的,采用IO多路复用机制;

        3、丰富的数据类型,支持字符串、散列、列表、集合、有序集合等;

        4、支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载;

        5、主从复制,哨兵,高可用;

        6、可以用作分布式锁;

        7、可以作为消息中间件使用,支持发布订阅。

        RedisTemplate主要支持String,List,Hash,Set,ZSet这几种方式的参数,其对应的方法分别是opsForValue()、opsForList()、opsForHash()、opsForSet()、opsForZSet()。

Redis支持的数据类型:

        string:最基本的数据类型,一个key对应一个value,二进制安全的字符串,string类型的值最大能存储512M。

        list:简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。数据结构:list底层是一个双向链表,因此可以用来当做消息队列用。

        set:string类型的无序集合,不存在重复的元素。底层通过hashtable实现。应用场景:set对外提供的功能和list一样是一个列表,特殊之处在于set自动去重,而且set提供了判断某个成员是否在一个set集合中。

        zset:string类型的已排序的集合。且不允许重复的元素。

        hash:key/value对的一种集合。

Redis key的过期时间和永久有效分别怎么设置?

       redisTemplate的expire()方法和persist()方法。

为什么Redis需要把所有数据放到内存中?

      redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。

Redis内存回收机制

      Redis过期策略:删除过期时间的key值。

      1.定时过期每个设置过期时间的key都需要创建一个定时器到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

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

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

     Redis中同时使用了惰性过期定期过期两种过期策略。

     Redis淘汰策略:内存使用到达maxmemory(redis.conf的server.maxmemory参数)上限时触发内存淘汰数据。redis源码里面REDIS_DEFAULT_MAXMEMORY 的默认值是0,即最大可用内存默认没有设置最大值,即不管用户存放多少数据到 Redis 中,Redis 也不会对可用内存进行检查,直到 Redis 实例因内存不足而崩溃也无作为。

     1.volatile-lru从已设置过期的数据集中挑选最近最少使用的淘汰

     2.volatile-ttl从已设置过期的数据集中挑选将要过期的数据淘汰

     3.volatile-random从已设置过期的数据集中任意挑选数据淘汰

     4.allkeys-lru从数据集中挑选最近最少使用的数据淘汰

     5.allkeys-random从数据集中任意挑选数据淘汰

     6.noenviction(默认策略):禁止淘汰数据,若超过最大内存,返回错误信息

Redis支持哪几种持久化方式

        RDB持久化 原理是将Redis在内存中的数据记录定时dump到磁盘上的RDB文件。 指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

       AOF(append only file)持久化 原理是将Redis的操作日志以追加的方式写入文件。 以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。当服务器重启的时候会重新执行这些命令来恢复原始的数据。AOF命令以Reids协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。Redis只要从头到尾执行一次AOF所有的命令就可以恢复数据

      redis持久化策略可在redis.conf中配置(默认情况下是RDB方式)。如下:

Redis两种持久化方式优缺点?

       Redis所有数据保存在内存中,对数据的更新将异步地保存到磁盘上,使得数据在Redis重启之后仍然存在。这么做这有什么实际意义呢?将数据存储到硬盘是为了以后可以重用数据,将数据进行备份,可以在系统故障的时候从备份进行恢复。还有一点,存储在Redis里面的数据可能是经过复杂运算而得出的结果,把这些数据进行存储,方便后续的使用,以达到“空间换时间”的效果。

RDB(快照)持久化

       Redis通过创建快照来获得存储在内存里面的数据在某个时间节点上的副本

触发机制:

      save(同步)

      bgsave(异步)

      自动

      通过手动调用SAVE或者BGSAVE命令或者配置条件触发,将数据库某一时刻的数据快照,生成RDB文件实现持久化。

        优点:RDB文件紧凑,体积小网络传输快,适合全量复制恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小

        缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差。

AOF持久化

       AOF持久化将被执行的写、删除命令写到AOF文件的末尾,以达到记录数据的目的Redis只要从头到尾重新执行一次AOF所有的命令就可以恢复数据

AOF三种策略:

AOF重写

       随着Redis的运行,被执行的写命令不断同步到AOF文件中,AOF文件的体积越来越大,极端情况将会占满所有的硬盘空间。如果AOF文件体积过大,还原的过程也会相当耗时。为了解决AOF文件不断膨胀的问题,需要移除AOF文件中的冗余命令来重写AOF

AOF重写的两种实现方式:

      1.bgrewriteaof命令(手动)

      bgrewriteaof命令和bgsave命令的工作原理相似:Redis创建一个子进程,然后由子进程负责对AOF文件进行重写。

     2.AOF重写配置(自动)

     优点:与RDB持久化相对应,AOF的优点在于支持秒级持久化、兼容性好

     缺点:文件大,恢复速度慢,对性能影响大

如何选择Redis持久化方式策略?

       在实际生产环境中,根据数据量、应用对数据的安全要求、预算限制等不同情况,会有各种各样的持久化策略;如完全不使用任何持久化、使用快照持久化或AOF持久化的一种,或同时开启快照持久化和AOF持久化等。此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机master和从机slave可以独立的选择持久化方案。

    (1)如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化

    (2)在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择快照持久化对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF

    (3)但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。在这种情况下,一种可行的做法是:master:完全关闭持久化,这样可以让master的性能达到最好slave:关闭快照持久化,开启AOF(如果对数据安全要求不高,开启快照持久化关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。

Redis有哪些架构模式?讲讲各自的特点?

1.单机版

       单机模式顾名思义就是安装一个 Redis,启动起来,业务调用即可。例如一些简单的应用,并非必须保证高可用的情况下可以使用该模式。

优点:

     1.部署简单

     2.成本低,无备用节点

     3.高性能,单机不需要同步数据,数据天然一致性。

缺点:

   1.可靠性保证不好,单节点有宕机风险。

   2.单机高性能受限于CPU的处理能力,redis是单线程的。

   单机 Redis 能够承载的 QPS(每秒查询速率)大概在几万左右。

2.主从复制

       Redis 的复制(Replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器Master),而通过复制创建出来的复制品则为从服务器Slave)。 只要主从服务器之间的网络连接正常,主服务器就会将写入自己的数据同步更新给从服务器,从而保证主从服务器的数据相同

  数据的复制是单向的,只能由主节点到从节点,简单理解就是从节点只支持读操作,不允许写操作。主要是读高并发的场景下用主从架构。主从模式需要考虑的问题是:当 Master 节点宕机,需要选举产生一个新的 Master 节点,从而保证服务的高可用性。

优点:

      1.Master/Slave 角色方便水平扩展,QPS 增加,增加 Slave 即可;

      2.降低 Master 读压力,转交给 Slave 节点;

      3.主节点宕机,从节点作为主节点的备份可以随时顶上继续提供服务;

缺点:

     1.可靠性保证不是很好,主节点故障到选举出新的主节点过程中无法提供写入服务

     2.没有解决主节点写的压力

     3.数据冗余(为了高并发、高可用和高性能,一般是允许有冗余存在的);

     4.一旦主节点宕机,从节点晋升成主节点,需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预

     5.主节点的写能力受到单机的限制;

     6.主节点的存储能力受到单机的限制。

3.哨兵

        主从模式中,当主节点宕机之后,从节点是可以作为主节点顶上来继续提供服务,但是需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。

  于是,在 Redis 2.8 版本开始,引入了哨兵(Sentinel)这个概念,在主从复制的基础上,哨兵实现了自动化故障恢复。如上图所示,哨兵模式由两部分组成,哨兵节点和数据节点:

哨兵节点:哨兵节点是特殊的 Redis 节点,不存储数据;

数据节点:主节点和从节点都是数据节点。

        哨兵节点和数据节点数量并一定是1:1的。

  Redis Sentinel 是分布式系统中监控 Redis 主从服务器,并提供主服务器下线时自动故障转移功能的模式。其中三个特性为:

       监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常;

      提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知;

      自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

Sentinel 中的关键名词:

定时任务

Sentinel 内部有 3 个定时任务,分别是:

      每 1 秒每个 Sentinel 对其他 Sentinel 和 Redis 节点执行 PING 操作(监控),这是一个心跳检测,是失败判定的依据。

      每 2 秒每个 Sentinel 节点会向所有被监视的master、slaver服务器发送包含当前sentinel、master信息的消息;

      每 10 秒每个 Sentinel 会对 Master 和 Slave 执行 INFO 命令,这个任务主要达到两个目的:发现 Slave 节点;确认主从关系。

主观下线

  所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断,即单个 Sentinel 认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。

  主观下线就是说如果服务器在给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 会将这个服务器标记为主观下线(SDOWN)。

客观下线

  客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断,并且通过命令互相交流之后,得出的服务器下线判断,然后开启 failover。

  只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线(ODOWN)。只有当 Master 被认定为客观下线时,才会发生故障迁移。

仲裁

  仲裁指的是配置文件中的 quorum 选项。某个 Sentinel 先将 Master 节点标记为主观下线,然后会将这个判定通过 sentinel is-master-down-by-addr 命令询问其他 Sentinel 节点是否也同样认为该 addr 的 Master 节点要做主观下线。最后当达成这一共识的 Sentinel 个数达到前面说的 quorum 设置的值时,该 Master 节点会被认定为客观下线并进行故障转移。

  quorum 的值一般设置为 Sentinel 个数的二分之一加 1,例如 3 个 Sentinel 就设置为 2。

哨兵模式工作原理:

      每个 Sentinel 以每秒一次的频率向它所知的 Master,Slave 以及其他 Sentinel 节点发送一个 PING 命令;

       如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过配置文件 own-after-milliseconds 选项所指定的值,则这个实例会被 Sentinel 标记为主观下线

       如果一个 Master 被标记为主观下线,那么正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 是否真的进入主观下线状态;

       当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下线状态,则 Master 会被标记为客观下线

       如果 Master 处于 ODOWN 状态,则投票自动选出新的主节点。将剩余的从节点指向新的主节点继续进行数据复制;

     在正常情况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令;当 Master 被 Sentinel 标记为客观下线时,Sentinel 向已下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次;

     若没有足够数量的 Sentinel 同意 Master 已经下线,Master 的客观下线状态就会被移除。若 Master 重新向 Sentinel 的 PING 命令返回有效回复,Master 的主观下线状态就会被移除。

优点:

     不需要手动修改应用方的主节点地址(Sentinel模型下配置的是节点集合,不是单个主节点,从中自动获取主节点信息),也不需要手动命令所有从节点指向新的主节点

     哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都有;

     哨兵模式的主从可以自动切换,系统更健壮,可用性更高;

     Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

缺点:

      主从切换需要时间,会丢失数据

      还是没有解决主节点写的压力

      主节点的写能力,存储能力受到单机的限制

      动态扩容困难复杂,对于集群,容量达到上限时在线扩容会变得很复杂。

哨兵模式client端连接配置:

4.集群模式

        假设上千万、上亿用户同时访问 Redis,QPS 达到 10 万+。这些请求过来,单机 Redis 直接就挂了。系统的瓶颈就出现在 Redis 单机问题上,此时我们可以通过主从复制解决该问题,实现系统的高并发。

  主从模式中,当主节点宕机之后,从节点是可以作为主节点顶上来继续提供服务,但是需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。于是,在 Redis 2.8 版本开始,引入了哨兵(Sentinel)这个概念,在主从复制的基础上,哨兵实现了自动化故障恢复

  哨兵模式中,单个节点的写能力,存储能力受到单机的限制,动态扩容困难复杂。于是,Redis 3.0 版本正式推出 Redis Cluster 集群模式,有效地解决了 Redis 分布式方面的需求。Redis Cluster 集群模式具有高可用可扩展性分布式容错等特性。

       Redis Cluster 采用无中心结构每个节点都可以保存数据和整个集群状态,每个节点都和其他所有节点连接。Cluster 一般由多个节点组成,节点数量至少为 6 个才能保证组成完整高可用的集群,其中三个为主节点,三个为从节点。三个主节点会分配槽,处理客户端的命令请求,而从节点可用在主节点故障后,顶替主节点

  如上图所示,该集群中包含 6 个 Redis 节点,3 主 3 从,分别为 M1,M2,M3,S1,S2,S3。除了主从 Redis 节点之间进行数据复制外,所有 Redis 节点之间采用 Gossip 协议进行通信,交换维护节点元数据信息。

        该模式下可以做读写分离,但是一般不这么做。一般都是直接在master上进行读写操作(毕竟有多个master),slave只是作数据备份。Jedis对Cluster 的读写分离支持得不是很好。

  总结下来就是:读请求分配给 Slave 节点,写请求分配给 Master数据同步从 Master 到 Slave 节点

分片:

      单机、主从、哨兵的模式数据都是存储在一个节点上,其他节点进行数据的复制。而单个节点存储是存在上限的,集群模式就是把数据进行分片存储,当一个分片数据达到上限的时候,还可以分成多个分片。

  Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,计算公式:HASH_SLOT = CRC16(key) % 16384每一个节点负责维护一部分槽以及槽所映射的键值数据。

       Redis Cluster 提供了灵活的节点扩容和缩容方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis Cluster 管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。

  简单的理解就是:扩容或缩容以后,槽需要重新分配,数据也需要重新迁移,但是服务不需要下线。

为什么虚拟哈希槽要设置为16384个?

        1.如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。

        2.redis的集群主节点数量基本不可能超过1000个。

        3.槽位越小,节点少的情况下,压缩率高。

        

        Redis Cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点复制主节点数据备份,当这个主节点挂掉后,就会通过这个主节点的从节点选取一个来充当主节点,从而保证集群的高可用。

  集群有 A、B、C 三个主节点,如果这 3 个节点都没有对应的从节点,如果 B 挂掉了,则集群将无法继续,因为我们不再有办法为 5501 ~ 11000 范围内的哈希槽提供服务。

  所以我们在创建集群的时候,一定要为每个主节点都添加对应的从节点。比如,集群包含主节点 A、B、C,以及从节点 A1、B1、C1,那么即使 B 挂掉系统也可以继续正确工作。

  因为 B1 节点属于 B 节点的子节点,所以 Redis 集群将会选择 B1 节点作为新的主节点,集群将会继续正确地提供服务。当 B 重新开启后,它就会变成 B1 的从节点。但是请注意,如果节点 B 和 B1 同时挂掉,Redis Cluster 就无法继续正确地提供服务了。

优点:

    无中心架构;

    可扩展性,数据按照 Slot(槽) 存储分布在多个节点,节点间数据共享,节点可动态添加或删除,可动态调整数据分布;

     高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本。

    实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升

缺点:

     数据通过异步复制,无法保证数据强一致性

     集群环境搭建复杂,不过基于 Docker 的搭建方案会相对简单

集群模式client端连接配置:

1.Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
2.Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

Redis集群的主从复制是怎样的?

主从复制的大致步骤如下:

        1.从节点执行slaveof[masterIP][masterPort]指令,保存主节点信息。

        2.从节点中的定时任务发现主节点信息,建立和主节点的socket连接。

        3.从节点发送PING信号,主节点返回PONG,确认两边能互相通信。

        4.建立连接后,主节点将所有数据发送给从节点(数据同步)。

        5.主节点把当前的数据同步给从节点后,便完成了数据的复制;接下来,主节点会把持续的写命令发送给从节点,保证主从数据的一致性。

        上诉的4和5步骤中,涉及到了数据的全量复制和增量。

全量复制和部分复制(https://www.cnblogs.com/cooffeeli/p/redis_master_slave.html):

        在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制

        1.全量复制用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。

        2.部分复制用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。

全量复制

 Redis通过psync命令进行全量复制的过程如下:
        1. 从节点向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行部分复制;
       2. 主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件(主服务器中缓存数据的快照),并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令。
        3. 主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态。
        4. 主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态。
        5. 如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态。

部分复制

        由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步。
部分复制的实现,依赖于三个重要的概念:
       1. 复制偏移量
       2. 复制积压缓冲区
       3. 服务器运行ID(runid)

复制偏移量:

        执行复制的双方,主从节点,分别会维护一个复制偏移量offset:
        主节点每次写入了N字节数据后,将修改自己的复制偏移量offset = offset+N
        从节点每次从主节点同步了N字节数据后,将修改自己的复制偏移量offset = offset+N

        offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。

复制积压缓冲区:

        主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,其默认大小为1MB。
        在主节点进行命令传播时,不仅会将写命令同步到从节点,还会将写命令写入复制积压缓冲区
        由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。因此,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制

        为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size)。

从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:
        1. 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;
       2. 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。

服务器运行ID(runid):

        每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成(每次启动都不一样),run_id用来唯一识别一个Redis节点。主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。
从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:
        1. 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
        2. 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制

psync命令执行过程中,主从节点如何确定使用全量复制还是部分复制的?

psync命令流程图:

psync命令的大体流程如下:
        如果从节点之前没有复制过任何主节点,或者之前执行过slaveof no one命令,从节点就会向主节点发送psync命令(psync ? -1,?表示第一次进行主从复制,还不知道主节点的run_id,-1表示offset。),请求主节点进行数据的全量同步。
        如果前面从节点已经同步过部分数据,此时从节点就会发送psync {runid} {offset}命令给主节点,其中runid是上一次主节点的运行ID,offset是当前从节点的复制偏移量.

主节点收到psync命令后,会出现以下三种可能:
        主节点返回 fullresync {runid} {offset}回复,表示主节点要求与从节点进行数据的完整全量复制,其中runid表示主节点的运行ID,offset表示当前主节点的复制偏移量。如果从节点发送的是psync ? -1命令,这是需要将主节点的run_id进行保存。
        如果主服务器返回 +continue,表示主节点与从节点会进行部分数据的同步操作,将从服务器缺失的数据复制过来即可。
        如果主服务器返回 -err,表示主服务器的Redis版本低于2.8,无法识别psync命令,此时从服务器会向主服务器发送sync命令,进行完整的数据全量复制

优点:

     a. 采用异步复制的方式;

     b. 一个master服务器可以存着多个slave服务器;每个slave服务器可以接受其他slave服务器的连接;

     c. 无论对于master、slave服务器都是非阻塞的,master服务器进行主从复制期间时,master服务器依然可以处理外部访问请求而slave服务器依然可以处理外部的查询请求,但是查询的结果为旧数据。

缺点:

      a. 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

      b. 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成

      c. Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。

      d. Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。

      e. Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据。

Redis集群会有写操作丢失吗?为什么?

       Redis Cluster 不保证强一致性,在一些特殊场景,客户端即使收到了写入确认,还是可能丢数据的。

        1.redis集群只是保证在写的时候总能找到一台机器往上面写,但redis是内存数据库,数据是先写到内存上的,如果突然闪断或者什么情况,导致数据没有写入日志文件,就会出现丢失

        2.过期key被清除

        3.内存不足,导致redis会清除掉部分key节省空间。

        4.异步复制。数据写入到master后,master与slaver之间进行数据同步时,master挂掉了,选举的新master可能还没有同步到该数据,造成数据丢失。

        4.网络分区(https://segmentfault.com/a/1190000021147037

Redis如何做内存优化

1.设置内存上限

        使用maxmemory参数限制最大可用内存,当超出内存上限maxmemory时使用LRU等删除策略释放空间以及防止所用内存超过服务器物理内存。

2.配置合理的内存回收策略。

3.降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度

        在完整描述业务情况下,键值越短越好。值对象缩减比较复杂,应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。

什么是缓存穿透?如何避免?

缓存穿透

       而缓存穿透是指缓存和数据库中都没有的数据,这样每次请求都会去查库,不会查缓存,如果同一时间有大量请求进来的话,就会给数据库造成巨大的查询压力,甚至击垮 db 系统。。

如何避免?

       进行参数校验,比如查询数据库中自增主键id为-1的信息,而该id是不合法、不存在的,因此可以在接口层进行参数校验,不合法直接就返回了。

        缓存空对象。对查询结果为空的情况也进行缓存,缓存时间设置短一点(5min或者更短一点之类的),或者该key对应的数据insert了之后清理缓存。

       使用布隆过滤器。对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

简单说下布隆过滤器:

      我们使用  布隆过滤器  对不存在的key进行过滤,其实数据量比较小的时候可以使用一个HashMap就能完成了,使用  布隆过滤器  主要是怎么大数据量的时候。

        布隆过滤器本身是一个很长的二进制向量,既然是二进制向量,那么里面存放的不是0,就是1。新建一个长度为16的布隆过滤器,默认值都是0,就像下面这样:

         我们需要添加一个数据的时候通过某种hash算法值%16映射到该过滤器中某一位,然后将该位置为1.比如:

         可以看出,仅仅从布隆过滤器本身而言,根本没有存放完整的数据,只是运用一系列随机映射函数计算出位置,然后填充二进制向量。

        优点:由于存放的不是完整的数据,所以占用的内存很少,而且新增,查询速度够快;
        缺点: 随着数据的增加,误判率随之增加;无法做到删除数据;只能判断数据是否一定不存在,而无法判断数据是否一定存在。

        java中布隆过滤器实现:

         参考文档:https://www.cnblogs.com/zc110/articles/13380446.html

                           https://blog.csdn.net/lifetragedy/article/details/103945885

什么是缓存击穿?如何避免?

       简单来说,缓存击穿是指一个 key 非常热点。在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就好像堤坝突然破了一个口,大量洪水汹涌而入。

如何避免?

       对于比较热点的数据,我们可以在缓存中设置这些数据永不过期;也可以在访问数据的时候,在缓存中更新这些数据的过期时间;如果是批量入库的缓存项,我们可以为这些缓存项分配比较合理的过期时间,避免同一时刻失效。

      还有一种解决方案就是:使用分布式锁,保证对于每个Key同时只有一个线程去查询后端的服务,某个线程在查询后端服务的同时,其他线程没有获得分布式锁的权限,需要进行等待。不过在高并发场景下,这种解决方案对于分布式锁的访问压力比较大。

        分布式锁:https://www.zhihu.com/question/300767410/answer/1749442787        

什么是缓存雪崩?何如避免?

缓存雪崩

       缓存雪崩和缓存击穿的概念有点类似,缓存击穿是针对单个热点数据,而缓存雪崩是针对大批量缓存数据的缓存失效,当缓存中大量缓存集中在某一个时间段失效,这样在失效的时候如果高并发下进行失效数据的查询,这样查询的任务就落在了数据库上,会给数据库带来很大压力,导致系统崩溃。

如何避免

       缓存雪崩的解决方案和击穿的思路一致,可以设置 key 不过期或者互斥锁的方式

      除此之外,因为是预防大面积的 key 同时失效,可以给不同的 key 过期时间加上随机值,让缓存失效的时间点尽量均匀,这样可以保证数据不会在同一时间大面积失效

      同时还可以结合主备缓存策略来让互斥锁的方式更加的可靠。

      主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。

      备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存。

双写一致性问题

        因为只要用缓存,就可能会涉及到缓存与数据库双存双写,只要是双写,就一定要考虑数据一致性的问题,首先,如果真的并发量不够的话,其实是可以允许缓存和数据库存在稍微的不一致
所以,这里有一个概念:最终一致性和强一致性如果是强一致性的,其实都不建议搞缓存
        同时,针对读的话,基本上都是采用的先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,处理方式的不同在于到底是先操作缓存还是先操作数据库。

最经典的缓存+数据库读写的模式,就是Cache Aside Pattern

       读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。

       更新的时候,先更新数据库,然后再删除缓存。

如果先更新DB再更新Cache,会带来脏数据:

        假设A、B两个线程,A先更新数据库后,B再更新数据库,然后分别进行更新缓存,但是B先更新缓存成功,A后更新缓存成功,这样就导致数据库是最新的数据(B的数据)但是缓存中是旧的脏数据(A的数据)。

如果先更新Cache再更新DB,会带来脏数据:

        假设A、B两个线程,A先更新缓存后,B再更新缓存,然后分别进行更新数据库,但是B先更新数据库成功,A后更新数据库成功,这样就导致缓存是最新的数据(B的数据)但是数据库中是旧的脏数据(A的数据)。

如果先删除Cache再更新DB,会带来脏数据:

        当有写线程A先删除了Cache,读线程B此时过来未从Cache中读取到数据,则去查DB,此时DB中的数据还未被线程A更新,然后查出来旧数据并重新缓存,这是线程A才去更新DB中数据。这是就会出现缓存数据和DB数据不一致的情况。

如果先更新DB再删除缓存,可以避免掉上面几种情况。但是也不是一定不会出现脏数据:

        缓存刚还失效,这时读线程A只有先去查询数据库,得到一个旧值。这时写线程B将新值写入数据库,并删除缓存(这时其实缓存中也没有了,已经失效了)。随后读线程A将查到的旧值写入缓存中,就出现了脏数据。这只是理论上的分析,其实这种情况只有当写的执行时间作比读的执行时间还要快的时候,才会出现,在实际中是很难出现的。

如果出现上诉情况,可以怎么办呢?延时异步双删策略。

延时:

        第一次删除缓存后,延时一段时间,比如1S,再判断一次缓存中有没有被写入新的数据,如果有则再次删除缓存。这么做,可以将延时这1S内造成的脏数据缓存给删除掉。如何确定延时时间呢?根据具体业务来分析。一般来说是根据项目的读数据业务逻辑的耗时,写数据完成后的休眠时间则在读数据业务逻辑的耗时基础上加几百ms即可。

异步:

        第二次删除作为异步删除,避免每次写操作的等待。

Redis为什么这么快?

        官方提供的数据可以达到10W+的QPS。       

        1.redis完全基于内存进行操作,绝大部分请求是纯粹的内存操作,非常迅速,数据存储在内存中。

        2.数据结构简单,对数据操作也简单。

        3.采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的CPU切换等。

        4.使用多路复用IO模型,非阻塞IO。

Redis的IO多路复用模型(https://www.cnblogs.com/reecelin/p/13538382.html):

        前面在学习netty接触了IO多路复用和Reactor模型。其实,redis就是基于Reactor模型开发了自己的网络事件处理器,称之为文件事件处理器(File Event Handle)。文件事件处理器由Socket、IO多路复用程序、文件事件分派器(dispather),事件处理器(handler)四部分组成。关于IO多路复用的相关知识,这方面可以参考我之前的一篇文章,这里就不多解释了。文件事件处理器的模型如下所示:

        IO多路复用程序会同时监听多个socket,当被监听的socket准备好执行accept、read、write、close等操作时,与这些操作相对应的文件事件就会产生。IO多路复用程序会把所有产生事件的socket压入一个队列中,然后有序地每次仅一个socket的方式传送给文件事件分派器文件事件分派器接收到socket之后会根据socket产生的事件类型调用对应的事件处理器进行处理

文件事件处理器分为几种:
        连接应答处理器:用于处理客户端的连接请求;
        命令请求处理器:用于执行客户端传递过来的命令,比如常见的set、lpush等;
        命令回复处理器:用于返回客户端命令的执行结果,比如set、get等命令的结果;

Redis和Memcached的区别?

        1.存储方式上:memcache会把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。redis有部分数据存在硬盘上,这样能保证数据的持久性。

        2.数据支持类型上:memcache对数据类型的支持简单,只支持简单的key-value,而redis支持五种数据类型。

        3.使用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。

        4.value的大小:redis可以达到1GB,而memcache只有1MB。

Redis为什么是单线程的?

        Redis单线程指的是网络请求模块使用了一个线程,即一个线程来处理所有网络请求,其他模块该使用多线程还是会多个线程。

        官方答案:因为Redis是基于内存的操作,CPU不是Redis的瓶颈Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

        redis采用单线程可以避免很多线程安全的问题。如果redis使用多线程来进行,那么我们在操作redis的list、hash等数据结构的时候,多线程就可能存在数据不安全的情况,就需要加锁,一旦加锁就影响了程序的执行速度。

参考文档:https://blog.csdn.net/xuan_lu/article/details/107214141

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
尚硅谷是一个教育机构,他们提供了一份关于Redis学习笔记。根据提供的引用内容,我们可以了解到他们提到了一些关于Redis配置和使用的内容。 首先,在引用中提到了通过执行命令"vi /redis-6.2.6/redis.conf"来编辑Redis配置文件。这个命令可以让你进入只读模式来查询"daemonize"配置项的位置。 在引用中提到了Redis会根据键值计算出应该送往的插槽,并且如果不是该客户端对应服务器的插槽,Redis会报错并告知应该前往的Redis实例的地址和端口。 在引用中提到了通过修改Redis的配置文件来指定Redis的日志文件位置。可以使用命令"sudo vim /etc/redis.conf"来编辑Redis的配置文件,并且在文件中指定日志文件的位置。 通过这些引用内容,我们可以得出结论,尚硅谷的Redis学习笔记涵盖了关于Redis的配置和使用的内容,并提供了一些相关的命令和操作示例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Redis学习笔记--尚硅谷](https://blog.csdn.net/HHCS231/article/details/123637379)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Redis学习笔记——尚硅谷](https://blog.csdn.net/qq_48092631/article/details/129662119)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值