redis的官网简介

Redis 是一个开放源码(BSD 许可)、内存中的数据结构存储,用作数据库、缓存和消息代理。Redis 提供数据结构,例如string字符串、hash散列、list列表、set集合、sorted set带有范围查询的排序集、bitmaps位图、hyperloglogs超级日志、geospatial indexes地理空间索引、 streams流。Redis 具有内置的复制、 Lua 脚本、 LRU 收回、事务和不同级别的磁盘持久性,并通过 Redis Sentinel 提供高可用性服务,并通过 Redis Cluster 提供自动分区。
您可以对这些类型运行原子操作,比如附加到字符串; 在散列中递增值; 将元素推入列表; 计算集合的交集、并集和差集; 或者获得排序集中排名最高的成员。
为了获得最佳性能,Redis 使用内存中的数据集。根据您的用例,您可以通过定期将数据集转储到磁盘或将每个命令附加到基于磁盘的日志中来持久化数据。如果您只需要一个功能丰富的网络化内存缓存,那么还可以禁用持久性。
Redis 还支持异步复制,具有非常快的非阻塞第一同步,自动重连接和部分重新同步的网络分割。
其他功能包括:

Redis 是用 ANSI c 编写的,在大多数 POSIX 系统中工作,比如 Linux、 * BSD 和 OS x,没有外部依赖性。Linux 和 OS x 是 Redis 开发和测试最多的两个操作系统,我们建议使用 Linux 进行部署。Redis 也许可以在诸如 SmartOS 这样的 solaris 衍生系统中工作,但是这种支持是最好的努力。没有对 Windows 构建的官方支持。

redis的快

redis为什么能风靡全球,首要原因就是因为它快,在网络发展如此迅速的时代,时间就是金钱,一个良好的快速反馈时间是第一要素。那为什么redis能这么快?

纯内存存储

redis将所有数据放在内存中,非数据同步正常工作中,是不需要从磁盘读取数据的,0次IO。内存响应时间大约为100纳秒,所以理论上redis是可以达到100*1000qps的。

I/O多路复用

I/O 多路复用(select/poll/epoll)机制中多路是指多个连接,复用是指一个线程多次重复使用,也就是一个线程处理多个 IO 流,redis采用的是epoll,在 redis 只运行单线程的情况下,该机制允许内核中同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求,一旦有请求到达就会交给 redis 线程处理,这样就实现了一个 redis 线程处理多个 IO 流的效果。看下图:

image.png

  • 一个 socket 客户端与服务端连接时,会生成对应一个套接字描述符(套接字描述符是文件描述符的一种),每一个 socket 网络连接其实都对应一个文件描述符。
  • 多个客户端与服务端连接时,redis 使用 「I/O 多路复用程序」 将客户端 socket 对应的 FD 注册到监听列表(一个队列)中。当客服端执行 read、write 等操作命令时,I/O 多路复用程序会将命令封装成一个事件,并绑定到对应的 文件描述符(FD ) 上。
  • 「文件事件处理器」使用 I/O 多路复用模块同时监控多个 FD 的读写情况(如下图),当 accept、read、write等文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器进行处理相关命令操作。
  • 整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,当其中一个 client 端达到写或读的状态,文件事件处理器就马上执行,从而就不会出现 I/O 堵塞的问题,提高了网络通信的性能。

image.png

单线程

单线程避免了线程上下文切换以及加锁释放锁带来的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。当然了,单线程也会有它的缺点,也是redis的噩梦:阻塞。如果执行一个命令过长,那么会造成其他命令的阻塞,对于Redis是十分致命的,所以Redis是面向快速执行场景的数据库。
这里需要提到的是,在redis4.0之后引入了多线程,像惰性删除,持久化、集群数据同步等操作,都是由额外的线程执行,而redis单线程是指主线程专注于网络 IO 和键值对读写。在redis6引入的多线程则是真正为了提高 I/O 的读写性能而引入的,它的主要实现思路是将主线程的 I/O 读写任务拆分给一组独立的子线程去执行,也就是说从 socket 中读数据和写数据不再由主线程负责,而是交给了多个子线程,这样就可以使多个 socket 的读写并行化了。这么做的原因就在于,虽然在 redis 中使用了 I/O 多路复用,但我们知道数据在内核态空间和用户态空间之间的拷贝是无法避免的,而数据的拷贝这一步是阻塞的,并且当数据量越大时拷贝所需要的时间就越多。所以多线程用于分摊同步读写 I/O 压力,从而提升 redis 的 qps。但是注意,redis 的命令本身依旧是由 redis 主线程串行执行的,只不过具体的读写操作交给独立的子线程去执行了,而这么做的好处就是不需要为 Lua 脚本、事务的原子性而额外开发多线程互斥机制,这样一来 redis 的线程模型实现起来就简单多了,因为和之前一样,所有的命令依旧是由主线程串行执行的,只不过具体的读写任务交给了子线程。下图是主线程跟io线程的交互情况:
image.png
redis6中,多线程机制默认是关闭的,如果想启动的话,需要修改 redis.conf 中的两个配置。第一个是设置io线程是否开启,第二个是设置其线程数。

  1. - io-thread-do-reads yes
  2. - io-threads 3

除了redis之外,node.js、nginx也是单线程,他们都是服务器高性能的典范。

redis的数据结构

string

它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M。底层用sds来使用,相对于c的原生字符串是char[]实现的好处之一是在获取长度时不需要遍历数据。
简单使用举例: set key value、get key等
应用场景:共享session、分布式锁,计数器、限流。
内部编码有3种,int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)

应用场景

  • 缓存功能:String字符串是最常用的数据类型,不仅仅是redis,各个语言都是最基本类型,因此,利用redis作为缓存,配合其它数据库作为存储层,利用redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
  • 计数器:许多系统都会使用redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
  • 统计多单位的数量:eg,uid:gongming count:0 根据不同的uid更新count数量。
  • 共享用户session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存cookie,这两种方式做有一定弊端,1)每次都重新登录效率低下 2)cookie保存在客户端,有安全隐患。这时可以利用redis将用户的session集中管理,在这种模式只需要保证redis的高可用,每次用户session的更新和获取都可以快速完成。大大提高效率。

hash

哈希类型是指v(值)本身又是一个键值对(k-v)结构
简单使用举例:hset key field value 、hget key field
内部编码:ziplist(压缩列表) 、hashtable(哈希表)
应用场景:缓存用户信息等。
注意点:如果开发使用hgetall,哈希元素比较多的话,可能导致redis阻塞,可以使用hscan。而如果只是获取部分field,建议使用hmget。

应用场景

  • 由于hash数据类型的key-value的特性,用来存储关系型数据库中表记录,是redis中哈希类型最常用的场景。一条记录作为一个key-value,把每列属性值对应成field-value存储在哈希表当中,然后通过key值来区分表当中的主键。
  • 经常被用来存储用户相关信息。优化用户信息的获取,不需要重复从数据库当中读取,提高系统性能。


    list

    image.png
    列表(list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。
    简单实用举例: lpush key value [value …] 、lrange key start end
    内部编码:ziplist(压缩列表)、linkedlist(链表)
    应用场景: 消息队列,文章列表。

应用场景

  • 消息队列:reids的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。
  • 文章列表或者数据分页展示的应用。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。

set

image.png

集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素。
简单使用举例:sadd key element [element …]、smembers key
内部编码:intset(整数集合)、hashtable(哈希表)
注意点:smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,可以使用sscan来完成。
应用场景: 用户标签,生成随机数抽奖、社交需求。

应用场景

  • 标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。
  • 共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。
  • 统计网站的独立IP。利用set集合当中元素不唯一性,可以快速实时统计访问网站的独立IP。

zset

已排序的字符串集合,同时元素不能重复。
简单格式举例:zadd key score member [score member …],zrank key member
底层内部编码:ziplist(压缩列表)、skiplist(跳跃表)
应用场景:排行榜,社交需求(如用户点赞)。

应用场景

  • 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
  • 用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

bitmaps

用一个比特位来映射某个元素的状态,在redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组。

应用场景

两种状态的统计都可以使用bitmaps,例如:统计用户活跃与非活跃数量、登录与非登录、上班打卡等等。

hyperloglogs

用来做基数统计算法的数据结构,如统计网站的浏览人数,帮程序自主去重。

应用场景

网页统计UV (浏览用户数量,同一天同一个ip多次访问算一次访问,目的是计数,而不是保存用户)
传统的方式,set保存用户的id,可以统计set中元素数量作为标准判断。但如果这种方式保存大量用户id,会占用大量内存,我们的目的是为了计数,而不是去保存id。

geospatial indexes

用来推算地理位置的信息,两地之间的距离,方圆几里的人等。

应用场景

  • 查看附近的人
  • 微信位置共享
  • 地图上直线距离的展示

    streams

    官方把它定义为:以更抽象的方式建模日志的数据结构。redis的streams主要是一个append only file的数据结构,至少在概念上它是一种在内存中表示的抽象数据类型,只不过它们实现了更强大的操作,以克服日志文件本身的限制。如果你了解MQ,那么可以把streams当做MQ。如果你还了解kafka,那么甚至可以把streams当做kafka。
    另外,这个功能有点类似于redis以前的Pub/Sub,但是也有基本的不同:

  • streams支持多个客户端(消费者)等待数据(Linux环境开多个窗口执行XREAD即可模拟),并且每个客户端得到的是完全相同的数据。

  • Pub/Sub是发送忘记的方式,并且不存储任何数据;而streams模式下,所有消息被无限期追加在streams中,除非用于显示执行删除(XDEL)。
  • streams的Consumer Groups也是Pub/Sub无法实现的控制方式。

redis的过期策略

redis有三种过期策略。

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 定期过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 惰性过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。

redis同时使用的是定期过期跟惰性过期。

redis的内存淘汰策略

内存并不是无限大的,当存储的容量达到一定限度时,redis就会采用内存淘汰策略来保护自己,主要有以下几种:

  • noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
  • volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰。
  • allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU算法进行淘汰。
  • volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
  • allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰。
  • volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据。
  • allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰。

redis的持久化机制

redis是基于内存的,如果挂了数据便全部丢失,所以便需要做持久化,把数据存到磁盘中。redis有rdbaof两种机制。

rdb

redis database,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。
rdb有2种触发机制,分别是自动触发和手动触发。

  • 自动触发,在 redis.conf 配置文件中的 SNAPSHOTTING 下,可配置相关策略。
    • save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave(这个命令下面会介绍,手动触发RDB持久化的命令),当然如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save “”。
    • stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了。
    • rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。
    • rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
    • dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在同一目录。

也就是说通过在配置文件中配置的 save 方式,当实际操作满足该配置形式时就会进行 RDB 持久化,将当前的内存快照保存在 dir 配置的目录中,文件名由配置的 dbfilename 决定。

  • 手动触发
    • save 该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。
    • bgsave 执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。

基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

rdb的优缺点

1、rdb是一个非常紧凑的文件,保存了redis在某个时间点的数据集,非常适合备份,体积比aof小,因为是数据的快照,基本上就是数据的复制,不用重新读取再写入内存。
2、rdb的工作原理是父进程在保存文件就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作,这样可以最大化提升redis的性能,但是当数据比较大时这个fork进程可能会非常耗时,造成redis阻塞。
3、rdb在恢复大数据集时比aof慢。
4、因为是根据时间来备份的,所以会有丢失数据的风险。

aof

append only file,将写操作追加到文件中,AOF 日志是写后日志,“写后”的意思是 redis 是先执行命令,把数据写入内存后,然后才记日志;里面记录的是指令执行的步骤,非常详细,描绘出了数据的变化过程。
在 redis.conf 配置文件中的 SNAPSHOTTING 下,可配置相关策略。

  • appendonly no 是否开启AOF机制,yes 代表开启
  • appendfilename “appendonly.aof” aof文件名
  • appendfsync xxx aof持久化策略的配置, xxx为alaways表示表示不执行同步,由操作系统自己选择时间保证数据同步到磁盘,速度最快;xxx为everysec表示每一秒执行一次同步,可能会导致丢失这1s数据;no表示每次写入内存后都执行同步,以保证数据同步到磁盘。
  • no-appendfsync-on-rewrite no 是否开启重写(当aof文件的大小超过所设定的阈值时,redis就会对aof文件的内容压缩。)
  • auto-aof-rewrite-percentage 100 当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写
  • auto-aof-rewrite-min-size 64mb 设置允许重写的最小aof文件大小
  • aof-use-rdb-preamble no 混合使用 aof和rdb的开关

    aof的优缺点

    1、可最大限度地保证数据的完整性。
    2、重写机制让日志文件更小。
    3、因为记录的是执行过程,所以文件会比rdb大许多。

redis的高可用

高可用的方案一般都是集群,redis有三种集群方式:主从模式,哨兵模式,集群模式

主从模式 master-slave

主从模式中,Redis部署了多台机器,有主节点,负责读写操作,有从节点,只负责读操作。从节点的数据来自主节点,实现原理就是主从复制机制,基本的步骤如下(蓝色代表slave发给master的命令,红色相反)

  • slave发送psync2命令到master。(命令:psync2
  • master接收到SYNC命令后,执行bgsave命令,生成RDB全量文件,并生成缓冲区(缓冲复制先进先出队列,默认1M)记录从现在开始执行的所有写命令。(命令: fullresync
  • master执行完bgsave后,向所有slave发送RDB快照文件。
  • slave收到RDB快照文件后,清空自己的数据,然后载入、解析收到的快照。
  • master快照发送完毕后,也就是全量复制结束后,会开始增量复制,向slave发送缓冲区中的写命令。
  • slave接受命令请求,并执行来自master缓冲区的写命令。
  • 后续slave只需要携带id跟offset(上次复制的偏移量)发给master,便可进行增量复制。如果存在就会发送continue给slave,如果不存在就会执行全量复制。(命令 continue )

哨兵模式 sentinel

主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。但是呢,一个哨兵进程对redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控redis节点,并且各个哨兵之间还会进行监控。下图是整体架构:
image.png

简单来说,哨兵模式就三个作用:

  • 发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态;
  • 哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;
  • 哨兵之间还会相互监控,从而达到高可用。

哨兵的工作模式如下:

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

当master被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是 Raft 算法,Raft 算法的基本思路是先到先得:即在一轮选举中,哨兵 A 向 B 发送成为领导者的申请,如果 B 没有同意过其他哨兵,则会同意 A 成为领导者。
选举出的领导者哨兵,开始进行故障转移操作,该操作大体可以分为 3 个步骤:

  1. 在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点,然后选择优先级最高的从节点(由 slave-priority 指定)。 如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择 runid 最小的从节点。
  2. 更新主从状态:通过 slaveof no one 命令,让选出来的从节点成为主节点;并通过 slaveof 命令让其他节点成为其从节点。
  3. 将已经下线的主节点设置为新的主节点的从节点,当它重新上线后,它会成为新的主节点的从节点。

集群模式 cluster

哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。 因此,cluster集群应运而生,它在redis3加入的,实现了redis的分布式存储。对数据进行分片,也就是说每台redis节点上存储不同的内容,来解决在线扩容的问题。并且它也提供复制故障转移的功能。
cluster集群通过Gossip协议进行通信,节点之前不断交换信息,交换的信息内容包括节点出现故障、新节点加入、主从节点变更信息、slot信息等等。常用的Gossip消息分为4种,分别是:ping、pong、meet、fail。

  • meet消息:通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。
  • ping消息:集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。
  • pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。
  • fail消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。

数据存储

cluster如何做到每个节点存储不同数据的呢,它采用的是hash slot插槽算法,插槽算法把整个数据库被分为16384个slot(槽),每个进入redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。
集群中的每个节点负责一部分的hash槽,比如当前集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:

  • 节点A负责0~5460号哈希槽
  • 节点B负责5461~10922号哈希槽
  • 节点C负责10923~16383号哈希槽

如果新增节点,那就会把其他节点的头部部分哈希槽一起平分给新节点,如果删除节点的话就平分到其他节点。
设计成16384个槽点是因为考虑到节点数不太可能超过1000,并且槽点越小,其压缩率就越高。

复制

cluster集群引入了主从复制,一个主节点对应一个或者多个从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机了。如果主节点宕机时,就会启用从节点。
当集群内节点出现故障时,通过故障转移,以保证集群正常对外提供服务。
redis集群通过ping/pong消息,实现故障发现。这个环境包括主观下线和客观下线

  • 主观下线: 某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。

image.png

  • 客观下线: 指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。
  • 假如节点A标记节点B为主观下线,一段时间后,节点A通过消息把节点B的状态发到其它节点,当节点C接受到消息并解析出消息体时,如果发现节点B的pfail状态时,会触发客观下线流程;
  • 当下线为主节点时,此时Redis Cluster集群为统计持有槽的主节点投票,看投票数是否达到一半,当下线报告统计数大于一半时,被标记为客观下线状态。
  • 故障恢复:故障发现后,如果下线节点的是主节点,则需要在它的从节点中选一个替换它,以保证集群的高可用。流程如下:
    • 资格检查:检查从节点是否具备替换故障主节点的条件。
    • 准备选举时间:资格检查通过后,更新触发故障选举时间。
    • 发起选举:到了故障选举时间,进行选举。
    • 选举投票:只有持有槽的主节点才有票,从节点收集到足够的选票(大于一半),触发替换主节点操作

    • mysql与redis保证双写一致性

延时双删

  1. 先删除缓存
  2. 再更新数据库
  3. 休眠一会(比如1秒),再次删除缓存。

这种方案还算可以,只有休眠那一会(比如就那1秒),可能有脏数据,一般业务也会接受的。但是如果第二次删除缓存失败呢?缓存和数据库的数据还是可能不一致。

删除缓存重试机制

  1. 写请求更新数据库
  2. 缓存因为某些原因,删除失败
  3. 把删除失败的key放到消息队列
  4. 消费消息队列的消息,获取要删除的key
  5. 重试删除缓存操作

读取binlog异步删除缓存

  • 可以使用阿里的canal将binlog日志采集发送到MQ队列里面
  • 然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性

redis的分布式锁

这个可用redisson实现,具体可查看redisson分布式锁详解

redis对于缓存的三大问题的处理

缓存雪崩

缓存雪崩是指在某一个时间段,缓存集中过期失效。此刻无数的请求直接绕开缓存,直接请求数据库。
造成缓存雪崩的原因,有以下两种:redis宕机大部分数据失效
对于缓存雪崩的解决方案有以下两种:

  • 搭建高可用的集群,防止单机的redis宕机。
  • 设置不同的过期时间,防止同一时间内大量的key失效。

缓存穿透

缓存穿透是指查询一条数据库和缓存都没有的一条数据,就会一直查询数据库,对数据库的访问压力就会增大;
解决方案:

  • 缓存空对象,将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
  • 布隆过滤器

布隆过滤器

Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展, 它的原理是:
当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:
如果这些点有任何一个 0,则被检索元素一定不在;如果都是 1,则被检索元素很可能在。举个例子:
image.png

但很明显它是不能删除元素的,后面提出了一个Count Bloom Filter ,新增一个数组来对每个下标进行计数,当计数数组满足都为0时,即可删除。

image.png

布隆过滤器在redis本身就支持,并且还提供了一个叫布谷鸟过滤器,最简单的布谷鸟过滤器是一维数组结构,会有两个 hash 算法将新来的元素映射到数组的两个位置。如果两个位置中有一个位置为空,那么就可以将元素直接放进去。但是如果这两个位置都满了,它就不得不「鸠占鹊巢」,随机踢走一个,然后自己霸占了这个位置。
被踢走的元素也会按照这种方式,根据两个hash算法来计算位置判断是否有空位,没空位就踢走一位,循环下去…当踢的次数太多时,就会扩容,对所有元素进行重新计算位置。

缓存击穿

是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,瞬间对数据库的访问压力增大。
解决方案:

  • 将数据热点数据设置成永久的,不设置失效时间。
  • 集群扩容,增加分片副本,均衡读流量。
  • 使用分布式锁,a、当缓存不命中时,在查询数据库前使用redis分布式锁,使用查询的key值作为锁条件;b、获取锁的线程在查询数据库前,再查询一次缓存。这样做是因为高并发请求获取锁的时候造成排队,但第一次进来的线程在查询完数据库后会写入缓存,之后再获得锁的线程直接查询缓存就可以获得数据;c、读取完数据后释放分布式锁。

通用场景

  • 缓存:热点数据缓存(例如报表、明星出轨),对象缓存、全页缓存、可以提升热点数据的访问数据。
  • 分布式共享数据:因为 redis 是分布式的独立服务,可以在多个应用之间共享数据,比如分布式锁、唯一ID等
  • 计数器:文章的阅读量、微博点赞数、允许一定的延迟,先写入Redis再定时同步到数据库
  • 限流:以访问者的ip和其他信息作为key,访问一次增加一次计数,超过次数则返回false
  • 统计:在线用户统计,留存用户统计
  • 购物车
  • 点赞、签到、打卡
  • 商品标签
  • 商品筛选
  • 用户关注、推荐模型
  • 排行榜