Redis

Redis不是简单得Key-Value存储,它实际上是一个数据结构服务器,支持不同类型的值,Redis的Key是二进制安全的,也就是说你可以用任何的二进制序列作为Key值,比如一个图片的二进制,甚至空字符串都是可以作为Key值。

Redis常规数据类型介绍:常规得数据类型支持5种
  • String: 二进制安全的,就是存进来之前什么样,获取的时候还是什么样,不会按照任何特殊的格式进行解析之后存储。
  • List:按照插入顺序排序的字符串元素集合,他们基本上都是链表
  • Hash:由Field和关联的Value组成的map,field和value都是字符串,例如 hset key field value
  • Sets:不重复且无序的字符串集合
  • Sorted sets:是有序的字符串集合,每个字符串都有一个score的浮动数值,元素根据这个score进行排序,例如:ZADD key score value,和Set不同的是,你可以获得批量数据,比如你想获取score在某个区间的值。
Redis实现队列或者异步队列

Redis能做消息队列还得益于其list数据结构的blpop(阻塞获取队列的第一个元素) brpop(阻塞获取队列的最后一个元素)接口以及Pub/Sub(发布/订阅)的某些接口,它们都是阻塞版的,所以可以用来做消息队列。(List : lpush / rpop)

  • 生产者消费者模式
    使用list结构作为队列,rpush生产消息,lpop消费消息,当lpop没有消息的时候,要适当sleep一会再重试。
    或者,不用sleep,直接用blpop指令或者brpop指令,在没有消息的时候,它会阻塞住直到消息到来。
  • 发布订阅者模式
    使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
    缺点:在消费者下线的情况下,生产的消息会丢失。此场景,建议用MQ。
为什么Redis这么高效?
  1. 纯内存操作
  2. 核心是基于非阻塞的IO多路复用机制
  3. C语言编写,一般来说,C语言相对于其他语言距离操作系统更近,执行速度相对会更快
  4. 单线程操作,避免了频繁的上下文切换,同时预防了多线程可能导致的竞争问题
Redis缓存预热,雪崩,穿透,击穿
  • 缓存预热:就是在系统上线之前,提前将部分缓存数据装进缓存中,避免上线后,大量请求进来直接访问数据库,然后再把数据缓存到数据中,
    解决方案:
  • 异步线程:异步线程扫描部分关键数据添加到缓存中。
  • 手动刷新:准备一些缓存刷新页面
  • 缓存雪崩:缓存管理,如果Redis挂了,所有请求全部直接请求在了数据库上,把数据库访问挂了。
    解决方案:
  • 事前:Redis高可用方案,主从+哨兵,Redis集群方案,避免全盘崩溃。
  • 事中:本地缓存ehcahe缓存+hystrix限流&降级对应的服务,避免MySQL被打死。
  • 事后:Redis持久化,一旦Redis重启,能自动快速恢复数据。
  • 缓存击穿:某个key非常热点,在集中式高并发的访问下,如果这个key突然失效,大量的请求就会直接去访问数据库,就像在墙上凿了个洞。
    解决方案:
  • 永不过期:设置Key永不过期。
  • 异步线程检测:我们可以将key的过期时间添加在value里面,使用一个异步线程定期扫描发现快过期了就更新。
  • 互斥锁:当缓存中Key不存在的时候,线程会去数据库查询数据并加载到缓存上,这时候可能会有很多个线程进行这个操作,所以我们可以去数据库查询的时候,设置上互斥锁,这样就会只有一个线程去访问数据库了,避免了数据库被大量访问打死。
  • 缓存穿透:遭受恶意攻击时,访问的数据,缓存中皆不存在,就会直接数据库请求,当恶意请求量过大时,也会直接把数据库打死。
    解决方案:
  • 暴力存储:当缓存中不存在时,去数据库查询也是空的时候,就直接给这个key设置一个默认值,放在缓存中就行了。
  • 布隆过滤器 : 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
Redis中数据的过期策略:定期删除+惰性删除 定时过期
  • 定时过期: Redis对设置了过期时间的key会到时间清除。缺点:在清除过期key的时候会占用大量的CPU资源
  • 惰性删除:Redis对过期的key,在再次访问的时候进行清除,缺点:会存在大量长期不常访问的key造成内存浪费
  • 定期删除: Redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,如果过期就清除。是对上面两种方案的折中。
Redis的内存淘汰机制(但内存不足时,继续写入数据,所采取的策略)
  • no-enviction(系统默认):禁止驱逐数据,直接报错,保护已存在的数据,
  • allkeys-random:从已写入的key中随机选择一个进行删除
  • allkeys-lru:从已写入的数据中选择最近最少使用的key进行删除
  • volatile-lru:从已写入的设置了过期时间的key中选择最近最少使用的key进行删除
  • volatile-random:从已写入的设置了过期时间的key中随机选择进行删除
  • volatile-ttl:从已写入的设置了过期时间的key‘中,选择过期时间比较靠前的key进行删除
Redis单例模式下的高可用

Redis在3.0版本以前只能单例部署,3.0以后redis支持集群部署。既然是集群肯定就存在着实例间的数据同步集群高可用等问题。

实例间的数据同步解决方案:主从复制(异步)
集群高可用解决方案:哨兵模式

主从复制(热备):数据同步——增量复制和全量复制

redis中sdiffstore的用法 redis fields_redis中sdiffstore的用法


Redis集群是主从结构:一主多从结构(Master—Slave),是一种读写分离的实现方式,Redis的主从复制是异步复制所以不能保证强一致性。

主从复制节点类型介绍:

  • Master:负责读写
  • Slave:备份主节点和提供读的能力
    主从复制结构,只能Master节点进行写,所以写的能力受限,但是可以添加Slave节点提高并发读的能力。
    主从复制
    优点:读写分离,增加Slave节点提高并发的能力。
    缺点Master能力是整个集群的写能力的瓶颈,Master的内存大小也是整个集群容量的瓶颈。
主从复制:增量复制和全量复制
  • 增量复制:如果从节点发送过去的runid和主节点的runid一致,则判断偏移量是否一致,如果不一致,则观察偏移量是否超过了repl_back_buffer中缓存的数据,如果超过则进行全量复制,如果未超过则将缓存中的数据发送给slave。
  • 全量复制:如果从节点是第一次进行复制的话,不知道master的runid,所以是?,偏移量是-1,主服务器验证runid和自身runid是否一致,如不一致,则进行全量复制;Master把自己的runid和offset(偏移量)发给slave,slave保存起来,使用bgsave生成RDB文件才,使用bgsave生成RDB文件。
Redis哨兵模式(Sentinel):主备切换

为什么说Redis的高可用解决方案是哨兵模式,因为我们知道Master节点挂了,我们需要重新选举出Master节点。这就依托于Redis的哨兵模式——Sentinel,来实现主备切换

Sentinel的主要功能包括:主节点存活检测主从运行情况检测自动化故障转移主备切换。Redis的哨兵模式的最小配置是一主一从。

redis中sdiffstore的用法 redis fields_lua_02


上图中的虚线箭头代表每个Sentinel实例都会监控所有Redis集群中的每个实例,

Sentinel集群

从上图我们不难发现Sentinel集群中的每个实例都是平等的,彼此之间通过心跳保持联系,如果一个挂了就直接挂掉了,因为都是级别相同的。

Sentinel的功能:

  • 监控:每一个Sentinel都监控着所有的Redis实例,
  • 通知:当被监控的Redis实例出现问题的时候,Sentinel可以通过特定的API脚本,向管理员或者应用程序发出通知。
  • 自动故障转移从节点挂了之后,挂了就挂了。主节点挂了之后,Sentinel从存活的从节点中选出新的主节点,并将其余的从节点重新指向新的主节点
  • 提供配置:在Redis Sentinel模式下,客户端应用子啊初始化时连接的是Sentinel,从中获取主节点的信息。
主观下线和客观下线

默认情况下,每个Sentinel节点会以每秒一次的频率和自己监控的redis节点和其他的Sentinel节点不断地发送Ping命令,然后根据回复判断节点是否在线。

  • 主观下线:主观下线适用于所有的主节点从节点,如果在down-after-milliseconds毫秒内,sentinel节点没有收到目标节点的有效回复,则认为该节点为主观下线
  • 客观下线:客观下线只适用于主节点sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 Sentinel 节点询问对该节点的状态判断。如果超过 quorum个数的节点判定主节点不可达,则该 Sentinel节点会判断 主节点 为 客观下线
Sentinel的通信命令

Sentinel连接到一个Redis实例的时候,会创建两个连接cmdpub/sub两个连接,Sentinel连接到Redis实例用的是cmd连接,用pub/sub连接去和同样监视着这个Redis实例的其他的Senrtinel实例。

SentinelRedis实例的命令主要包括:

命令

作用

PING

Sentinel向Redis实例发送PING命令,检查节点状态

INFO

Sentinel向Redis实例发送INFO命令,获取它的从节点

PUBLISH

Sentinel 向其监控的 Redis 节点 __sentinel__:hello 这个 channel 发布 自己的信息 及 主节点 相关的配置

SUBSCRIBE

Sentinel 通过订阅 Redis 主节点 和 从节点 的 __sentinel__:hello 这个 channnel,获取正在监控相同服务的其他 Sentinel 节点

SentinelSentinel交互命令,主要包括:

命令

作用

PING

Sentinel向Redis实例发送PING命令,检查节点状态

SENTINEL:is-master-down-by-add

和其他 Sentinel 协商 主节点 的状态,如果 主节点 处于 SDOWN 状态,则投票自动选出新的 主节点

Redis集群**主备切换(故障转移)**的流程:

  1. Sentinel节点以每秒一次的频率,向它所知道的主节点从节点、以及Sentinel节点实例发送```PING````命令
  2. 如果上面三种节点中,某个节点回复,超过down-after-milliseconds所指定的值,那么这个节点会被Sentinel标记为主观下线
  3. 如果一个节点被标记为主观下线,监视这个节点的其它所有Sentinel实例,会以每秒一次的频率确认该节点的确进入主观下线状态。
  4. 如果某个主节点在指定时间段内,有足够数量的Sentinel实例将其标记为客观下线,如果没有足够数量的Sentinel认同该节点主观下线,那么就会将客观下线标记移除,当开始回复Sentinel时,主观下线标记被移除。
  5. Sentinel会向标记为客观下线的主节点的所有从节点发送INFO命令的频率,从10秒一次改为1秒一次,
  6. 如果主节点是客观下线,并且当前没有进行主备切换,如果有正在进行的中的准备切换,那么它的切换开始时间到现在持续的时间已经超过failover-timeout (Redis的配置项,指定时间内完成故障转移)配置的 2 倍。
  7. 哨兵节点开始选举,当前哨兵会先对当前纪元+1,然后发起投票申请,申请中包含当前主节点(客观下线的节点),当前哨兵的ID和当前哨兵的纪元,
  8. 别的哨兵收到投票申请后,会先判断当前主节点存储的对应的Leader节点的纪元是否比发起申请的哨兵的纪元小,并且每个收到投票申请的哨兵的纪元也要比发起申请的哨兵纪元小,就返回发起申请的哨兵的ID(RunId),否则就会直接返回当前主节点(被标记为客观下线的节点)存储的Leader节点的id,
  9. 哨兵节点进行Leader选举,根据raft算法(一种分布一致性算法)从所有哨兵节点中选出Leader负责此次的主备切换,选主
  10. Leader实例会选取出新的主节点,将剩余的从节点指向新主节点进行数据复制
    哨兵模式选Leader详细解释
Redis集群模式

Redis集群是通过数据分区,根据算法将不同的key映射存储到不同的Redis节点上,从而解决了Redis内存容量受单节点所在机器内存大小的限制。

数据分片

Redis 集群使用数据分片(sharding) 而非 一致性哈希(consistency hashing) 来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:

节点 A 负责处理 0 号至 5500 号哈希槽。

节点 B 负责处理 5501 号至 11000 号哈希槽。

节点 C 负责处理 11001 号至 16384 号哈希槽。

redis中sdiffstore的用法 redis fields_redis批量删除key_03


如果有一个节点挂掉,会动态的把这个Redis负责的哈希槽位平均分配到另外存活的Redis节点上,所以动态增加删除上面的节点并不会造成集群的下线,虽然上面是多个Redis节点,但是仍然不支持同时处理多个键的命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的错误。