redis 大总结

什么是redis

1.Redis 是用C语言开发的一个开源的高性能键值对( key-value )内存数据库,它是一种NoSQL 数据库。

2.它是【单进程单线程】的内存数据库,所以说不存在线程安全问题。(ps:6.x版本为io多线程,工作线程单线程)

3.它可以支持并发 10W QPS,所以说性能非常优秀。因为底层采用了【IO多路复用(NIO思想)】

4.相比于同类产品memcache,更优秀的读写性能,及丰富的数据类型。

5.五种数据类型来存储【值】:字符串类型(string)、散列类型(hash)、列表类型(list)、集合类型(set)、有序集合类型(sortedset、zset)

官网地址    中文官网地址 redis在线测试

redis应用场景

1.内存数据库(登录信息、购物车信息、用户浏览记录等)
2.缓存服务器(商品数据、广告数据等等)(最多使用)
3.解决分布式集群架构中的session 分离问题( session 共享)
3.任务队列(秒杀、抢购、12306等等)
4.分布式锁的实现
5.支持发布订阅的消息模式
6.应用排行榜(有序集合)
7.网站访问统计
8.数据过期处理(可以精确到毫秒)

reids安装(limux)

1.安装C语言需要的GCC 环境

yum install -y gcc-c++
yum install -y wget

2.下载并解压

wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar -zxf redis-5.0.4.tar.gz

3.编译redis源码

cd redis-5.0.4
make

4.安装

make install PREFIX=/redis

5.启动&关闭

#前端启动
./redis-server

#关闭
ctrl+c
=====================================================================

#后端启动(守护进程)
cp /redis-5.0.4/redis.conf /redis/bin/   拷贝redis.conf

vim redis.conf 
# 将`daemonize`由`no`改为`yes`
daemonize yes
# 是否开启保护模式,由yes该为no
protected-mode no

#启动服务
./redis-server redis.conf

#关闭服务
./redis-cli shutdown

6.其他命令

redis-server :启动redis 服务
redis-cli :进入redis 命令客户端
redis-benchmark : 性能测试的工具
redis-check-aof : aof 文件进行检查的工具
redis-check-dump : rdb 文件进行检查的工具
redis-sentinel : 启动哨兵监控服务

redis为什么快?

       redis为了追求高性能,做了非常多的优化, 根据官方基测数据,redis的QPS可以达到10w。为了追求极致的快,redis的优化手段总结如下:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 1.基于内存的实现

        计算机各部分访问时间,如下图所示:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

         redis是基于内存实现的,读写都在内存中完成,操作不会因为磁盘IO速度的限制,速度非常快。

2.I/O 多路复用模型

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

        Redis 采用 I/O 多路复用技术,并发处理连接。采用了 epoll + 自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 IO 上浪费一点时间。

        它的基本原理是,内核不是监视应用程序本身的连接,而是监视应用程序的文件描述符。

        Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。

3.单线程模型

        官方答案:因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。

        Redis 的单线程指的是 Redis 的网络 IO 以及键值对指令读写是由一个线程来执行的。 对于 Redis 的持久化、集群数据同步、异步删除等都是其他线程执行。

优势:

  1. 不会因为线程创建导致的性能消耗;

  2. 避免上下文切换引起的 CPU 消耗,没有多线程切换的开销;

  3. 避免了线程之间的竞争问题,比如添加锁、释放锁、死锁等,不需要考虑各种锁问题。

  4. 代码更清晰,处理逻辑简单。

4.高效的数据结构

redis常用的5种数据类型与应用场景如下:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

        为了追求速度,redis设计了不同的数据结构来存储这些数据类型,底层数据结构主要有6种,它们和5种数据类型的关系如下:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 Redis hash 字典

        Redis 整体就是一个 哈希表来保存所有的键值对,无论数据类型是 5 种的任意一种。哈希表,本质就是一个数组,每个元素被叫做哈希桶,不管什么数据类型,每个桶里面的 entry 保存着实际具体值的指针。

        整个数据库就是一个全局哈希表,而哈希表的时间复杂度是 O(1),只需要计算每个键的哈希值,便知道对应的哈希桶位置,定位桶里面的 entry 找到对应数据,这个也是 Redis 快的原因之一。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 全局hash表冲突

1.全局hash表中采用链式hash:同一个桶里面使用链表保存

2.redis总共使用两个全局hash表:默认只使用其中一个表存数据,当数据量大时触发rehash:给另一个全局hash表申请更大空间,将数据重新映射到这个hash表中,释放先前那个hash表。

3.rehash过程不是一次性的,redis采用渐进式,将rehash分散到多次请求过程中(客户端请求时,先从 hash 表 1 中第一个索引开始,将这个位置的所有数据拷贝到 hash 表 2 中),避免长时间阻塞。

SDS 简单动态字符

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 

         redis没有直接使用c语言的字符串结构,而是自己实现了一个为SDS的结构来存储字符串。

它们主要区别:

1.O(1) 时间复杂度获取字符串长度

2.空间预分配:如果对 SDS 修改后,len 的长度小于 1M,那么程序将分配和 len 相同长度的未使用空间;如果对 SDS 修改后 len 长度大于 1M,那么程序将分配 1M 的未使用空间。

3.惰性空间释放:当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配。

4.存储二进制数据:在 Redis 中不仅可以存储 String 类型的数据,也可能存储一些二进制数据。二进制数据并不是规则的字符串格式,其中会包含一些特殊的字符如 '\0',在 C 中遇到 '\0' 则表示字符串的结束,但在 SDS 中,标志字符串结束的是 len 属性。

zipList 压缩列表

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

1.list、hash和zset底层存储结构之一

2.当一个列表只包含少量列表项时,并且每个列表项是小整数值或短字符串,那么Redis会使用压缩列表来做该列表的底层实现。

3.Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

双端列表(LinkedList)

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

redis链表特点:双向 、无环、带链表长度计数器、多态(使用 void* 指针来保存节点值,可以保存各种不同类型的值)

quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 

跳跃表(skipList) 

1.zset类型的排序功能底层的数据结构

2.查找一个节点时,我们只需从高层到低层,一个个链表查找,每次找到该层链表中小于等于目标节点的最大节点,直到找到为止。由于高层的链表迭代时会“跳过”低层的部分节点,所以跳跃表会比正常的链表查找少查部分节点,这也是skiplist名字的由来。

3.跳跃表支持平均 O(logN)、最坏 O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点

4.每次插入数据时,抛硬币方式确定,要不要往上增加一层。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 整数数组(intset)

 1.当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现。

typedef struct intset{
     //编码方式
     uint32_t encoding;
     //集合包含的元素数量
     uint32_t length;
     //保存元素的数组
     int8_t contents[];
}intset;

对象

1.Redis 使用对象(redisObject)来表示数据库中的键值,当我们在 Redis 中创建一个键值对时,至少创建两个对象,一个对象是用做键值对的键对象,另一个是键值对的值对象。

typedef struct redisObject{
    //类型
   unsigned type:4;
   //编码
   unsigned encoding:4;
   //指向底层数据结构的指针
   void *ptr;
    //...
 }robj;

5.总结

  1. 纯内存操作,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在 IO 上,所以读取速度快。

  2. 整个 Redis 就是一个全局 哈希表,他的时间复杂度是 O(1),而且为了防止哈希冲突导致链表过长,Redis 会执行 rehash 操作,扩充 哈希桶数量,减少哈希冲突。并且防止一次性 重新映射数据过大导致线程阻塞,采用 渐进式 rehash。巧妙的将一次性拷贝分摊到多次请求过程后总,避免阻塞。

  3. Redis 使用的是非阻塞 IO:IO 多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,Redis 采用自己实现的事件分离器,效率比较高。

  4. 采用单线程模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。

  5. Redis 全程使用 hash 结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。

  6. 根据实际存储的数据类型选择不同编码

redis持久化

        当redis机器宕机后,内存中的数据全部丢失,redis为了实现宕机后数据快速恢复,设计了两种数据持久化方式,分别是AOF(Append Only FIle)日志和 RDB 快照。

RDB快照

        在 Redis 执行「写」指令过程中,内存数据会一直变化。所谓的内存快照,指的就是 Redis 内存中的数据在某一刻的状态数据。

        Redis 通过定时执行 RDB 内存快照,这样就不必每次执行「写」指令都写磁盘,只需要在执行内存快照的时候写磁盘。既保证了速度的同时,还实现了持久化,宕机快速恢复。

        在做数据恢复时,直接将 RDB 文件读入内存完成恢复。

生成策略

        save:主线程执行,会阻塞。

        bgsave:调用 glibc 的函数fork产生一个子进程用于写入 RDB 文件,快照持久化完全交给子进程来处理,父进程继续处理客户端请求,生成 RDB 文件的默认配置。

写时复制技术 COW(Copy On Write) 

        当主线程执行写指令修改数据的时候,这个数据就会复制一份副本, bgsave 子进程读取这个副本数据写到 RDB 文件,所以主线程就可以直接修改原来的数据,这样在生成RDB文件期间,主线程还能正常对外提供服务,不会造成阻塞。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

         Redis 会使用 bgsave 对当前内存中的所有数据做快照,这个操作是子进程在后台完成的,这就允许主线程同时可以修改数据。

RDB开销

1.频繁生成 RDB 文件写入磁盘,磁盘压力过大。会出现上一个 RDB 还未执行完,下一个又开始生成,陷入死循环。

2.fork 出 bgsave 子进程会阻塞主线程,主线程的内存越大,阻塞时间越长。

优缺点

优点:RDB 采用二进制 + 数据压缩的方式写磁盘,文件体积小,数据恢复速度快。

缺点:系统宕机时,将丢失上一次RDB之后更新的数据。生成 RDB 文件频率不好把握,频率过低宕机丢失的数据就会比较多;太快,又会消耗额外开销。

AOF日志

        AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。

        宕机恢复时,Redis 实例可以通过顺序执行AOF所有的指令,也就是「重放」,来恢复 Redis 当前实例的内存数据结构的状态。

写前与写后日志对比

写前日志:在实际写数据之前,将修改的数据写到日志文件中,故障恢复得以保证。mysql的redolog就是写前日志。

写后日志:先执行「写」指令请求,将数据写入内存,再记录日志。redis采用写后日志的形式,避免了额外的检查开销,不需要对执行的命令进行语法检查。

日志记录格式

1.「*3」:表示当前指令分为三个部分,每个部分都是 「$ + 数字」开头,紧跟后面是该部分具体的「指令、键、值」。

2.「$数字」:表示这部分的命令、键、值多占用的字节大小。比如 「$3」表示这部分包含 3 个字节,也就是 「set」指令。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 写回策略

        为了提高文件的写入效率,当用户调用 write 函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。

        这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。

        系统提供了fsyncfdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

Redis 提供的 AOF 配置项appendfsync写回策略直接决定 AOF 持久化功能的效率和安全性。

1.always:同步写回,写指令执行完毕立马将 aof_buf缓冲区中的内容刷写到 AOF 文件。

2.everysec:每秒写回,写指令执行完,日志只会写到 AOF 文件缓冲区,每隔一秒就把缓冲区内容同步到磁盘。

3.no: 操作系统控制,写执行执行完毕,把日志写到 AOF 文件内存缓冲区,由操作系统决定何时刷写到磁盘。

优缺点

优点:执行成功才记录日志,避免了指令语法检查开销。同时,不会阻塞当前「写」指令。

缺点:由于 AOF 记录的是一个个指令内容,如果redis运行时间久了,日志文件将非常大,整个恢复过程就会非常缓慢。

重写机制(日志过大)

        Redis 提供了 bgrewriteaof指令用于对 AOF 日志进行瘦身,不会阻塞主线程。

        开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。

        重写机制可以实现多变一的功能。

        每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于遍历数据生成重写记录;使用两个日志保证在重写过程中,新写入的数据不会丢失,并且保持数据一致性。

        等到拷贝数据的所有操作记录重写完成后,重写缓冲区记录的最新操作也会写到新的 AOF 文件中。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 Redis 4.0 混合日志模型

        RDB模式下,会丢失较多数据,AOF模式下日志重放,数据恢复速度较慢。因此产生了一种混合双打模式。

        RDB 内存快照以稍微慢一点的频率执行,在两次 RDB 快照期间使用 AOF 日志记录期间发生的所有「写」操作。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 总结

        Redis 设计了 bgsave 和写时复制,尽可能避免执行快照期间对读写指令的影响,频繁快照会给磁盘带来压力以及 fork 阻塞主线程。

        避免日志过大,提供了 AOF 重写机制,根据数据库的数据最新状态,生成数据的写操作作为新日志,并且通过后台完成不阻塞主线程。

        综合 AOF 和 RDB 在 Redis 4.0 提供了新的持久化策略,混合日志模型。在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

redis主从架构

为了高可用,redis提供了主从模式,通过主从复制,将数据冗余一份到其他redis服务器中。

前者称为主节点 (master),后者称为从节点 (slave);数据的复制是单向的,只能由主节点到从节点。

为了保证副本数据的一致性,主从架构采用了读写分离的方式。

作用:

  1. 故障恢复:当主节点宕机,其他节点依然可以提供服务;

  2. 负载均衡:Master 节点提供写服务,Slave 节点提供读服务,分担压力;

  3. 高可用基石:是哨兵和 cluster 实施的基础,是高可用的基石。

全量同步:

第一阶段:建立连接协商同步

第二阶段:主库发送RDB文件给从库同步

第三阶段:发送同步期间新写命令到从库

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 replication buffer 太小会引发的问题:

1)当 master-slave 复制连接断开,master 会释放连接相关的数据。replication buffer 中的数据也就丢失了,此时主从之间重新开始复制过程。

2)还有个更严重的问题,主从复制连接断开,导致主从上出现重新执行 bgsave 和 rdb 重传操作无限循环。

主从库复制为何不使用 AOF 呢?相比 RDB 来说,丢失的数据更少。

  1. RDB 文件是二进制文件,网络传输 RDB 和写入磁盘的 IO 效率都要比 AOF 高。

  2. 从库进行数据恢复的时候,RDB 的恢复效率也要高于 AOF。

增量同步 

        用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。

 

        断开重连增量复制的实现奥秘就是 repl_backlog_buffer 缓冲区,不管在什么时候 master 都会将写指令操作记录在 repl_backlog_buffer 中,因为内存有限, repl_backlog_buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。

        master 使用 master_repl_offset记录自己写到的位置偏移量,slave 则使用 slave_repl_offset记录已经读取到的偏移量。

        当主从断开重连后,slave 会先发送 psync 命令给 master,同时将自己的 runIDslave_repl_offset发送给 master。master 只需要把 master_repl_offset与 slave_repl_offset之间的命令同步给从库即可。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 基于长连接的命令传播

        主库会通过这个连接将后续陆续收到的命令操作再同步给从库,使用长连接的目的就是避免频繁建立连接导致的开销。

维护心跳

        每隔指定的时间,主节点会向从节点发送 PING 命令,这个 PING 命令的作用,主要是为了让从节点进行超时判断。

       主---->从:PING                              从---->主:REPLCONF ACK

如何区分全量同步还是增量同步? 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 replication buffer 和repl_backlog_buffer区别

replication buffer 是主从库在进行全量复制时,主库上用于和从库连接的客户端的 buffer(不共享),而 repl_backlog_buffer 是为了支持从库增量复制,主库上用于持续保存写操作的一块专用 buffer。

repl_backlog_buffer是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的。主库和从库会各自记录自己的复制进度,所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset)发给主库,主库就可以和它独立同步。

总结 

  1. 主从复制的作用:AOF 和 RDB 二进制文件保证了宕机快速恢复数据,尽可能的防止丢失数据。但是宕机后依然无法提供服务,所以便演化出主从架构、读写分离。

  2. 主从复制原理:连接建立阶段、数据同步阶段、命令传播阶段;数据同步阶段又分为 全量复制和部分复制;命令传播阶段主从节点之间有 PING 和 REPLCONF ACK 命令互相进行心跳检测。

  3. 主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助。

redis哨兵集群(sentinel)

集群搭建:参考链接

一主两从三兵集群的集群结构如下

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 哨兵的主要任务

1.监控:持续监控 master 、slave 是否处于预期工作状态

2.自动切换主库:当 Master 运行故障,哨兵启动自动故障恢复流程:从 slave 中选择一台作为新 master。

3.通知:让 slave 执行 replicaof ,与新的 master 同步;并且通知客户端与新 master 建立连接。

哨兵也是一个 Redis 进程,只是不对外提供读写服务,通常哨兵要配置成单数

主观下线

        哨兵利用 PING 命令来检测master、 slave 的生命状态,如果是无效回复,哨兵就把其标记【主观下线】。

        如果检测到是 master 完蛋,这时候哨兵不能这么简单的标记「主观下线」,开启新掌门选举。

        为了防止误判,那就多个人一起投票判断。哨兵机制也是类似的,采用多实例组成的集群模式进行部署,这就是哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。     

客观下线   

        判断 master 是否下线不能只有一个「哨兵」说了算,只有过半的哨兵判断 master 已经「主观下线」,这时候才能将 master 标记为「客观下线」。

        只有 master 被判定为「客观下线」,才会进一步触发哨兵开始主从切换流程。

主观下线和客观下线的区别

       主观下线是哨兵自己认为节点宕机,而客观下线是不但哨兵自己认为节点宕机,而且该哨兵与其他哨兵沟通后,达到一定数量的哨兵都认为该哥们嗝屁了。

自动切换主库

        按照一定的 「筛选条件」 + 「打分」 策略,选出「最强王者」担任掌门。

        筛选条件:

        1.从库当前在线状态,下线的直接丢弃;

        2.评估之前的网络连接状态,如果从库总是和主库断连,而且断连次数超出了一定的阈值(10 次),我们就有理由相信,这个从库的网络状况并不是太好,就可以把这个从库筛掉了。

        打分:

        1.slave 优先级,通过 slave-priority 配置项

        2.offset进度差距

        3.slave runID,在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高

通知

1.让slave执行replicaof命令

2.通知客户端与新master连接

哨兵的主要任务

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

哨兵之间通信

         主要归功于 Redis 的 pub/sub 发布/订阅机制,通过master(群组)建了一个群,哨兵之间通过这个群建立联系。

        当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的 IP 地址和端口,从而相互发现建立连接。

        哨兵向 master 发送 INFO 命令, master 接收到命令后,便将 slave 列表告诉哨兵。哨兵根据 master 响应的 slave 名单信息与每一个 salve 建立连接,并且根据这个连接持续监控哨兵。

选择哨兵执行主从切换

        任何一个哨兵判断 master “主观下线”后,就会给其他哨兵基友发送 is-master-down-by-addr 命令,好基友则根据自己跟 master 之间的连接状况分别响应 Y 或者 N ,Y 表示赞成票, N 就是反对。多数赞成则标记master为“客观下线”。

        获得多数赞成票的哨兵可以向其他哨兵发送命令,申明自己想要执行主从切换。并让其他哨兵进行投票,投票过程就叫做 “Leader 选举”。

通过 pub/sub 实现客户端事件通知

总结

  • 基于 pub/sub 机制实现哨兵集群之间的通信;

  • 基于 INFO 命令获取 slave 列表,帮助 哨兵与 slave 建立连接;

  • 通过哨兵的 pub/sub,实现了与客户端和哨兵之间的事件通知。

 主从切换,并不是随意选择一个哨兵就可以执行,而是通过投票仲裁,选择一个 Leader,由这个 Leader 负责主从切换。

redis cluster集群

cluster集群搭建

        使用 Redis Cluster 集群,将数据分片存入不同数据库,解决了大数据量存储导致的各种慢问题,同时也便于横向拓展。

        cluster 集群是一种分布式数据库方案,集群通过分片(sharding)来进行数据管理(「分治思想」的一种实践),并提供复制和故障转移功能。

        将数据划分为 16384 的 slots,每个节点负责一部分槽位。槽位的信息存储于每个节点中。

        它是去中心化的,如图所示,该集群有三个 Redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

         三个节点相互连接组成一个对等的集群,它们之间通过 Gossip协议相互交互集群信息,最后每个节点都保存着其他节点的 slots 分配情况。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

cluster实现原理 

        Redis 3.0 开始,Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot),来处理数据和实例之间的映射关系。

        集群的整个数据库被分为 16384 个槽(slot),数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个槽。当 16384 个槽都分配完全,Redis 集群才能正常工作。

        Key 与哈希槽映射过程可以分为两大步骤:

  1. 根据键值对的 key,使用 CRC16 算法,计算出一个 16 bit 的值;

  2. 将 16 bit 的值对 16384 执行取模,得到 0 ~ 16383 的数表示 key 对应的哈希槽。

        Cluster 还允许用户强制某个 key 挂在特定槽位上,通过在 key 字符串里面嵌入 tag 标记,这就可以强制 key 所挂在的槽位等于 tag 所在的槽位。

复制与故障转移

        当 Master 下线,Slave 代替主节点继续处理请求。主从节点之间并没有读写分离, Slave 只用作 Master 宕机的高可用备份。

        Redis Cluster 可以为每个主节点设置若干个从节点,单主节点故障时,集群会自动将其中某个从节点提升为主节点。如果某个主节点没有从节点,那么当它发生故障时,集群将完全处于不可用状态。

故障检测

        Redis 集群节点采用 Gossip 协议(病毒侵入)来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。

        如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集群的大多数,就可以标记该节点为确定下线状态 (Fail),然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实,并立即对该失联节点进行主从切换。

故障转移

当一个 Slave 发现自己的主节点进入已下线状态后,从节点将开始对下线的主节点进行故障转移。

  1. 从下线的 Master 及节点的 Slave 节点列表选择一个节点成为新主节点。

  2. 新主节点会撤销所有对已下线主节点的 slot 指派,并将这些 slots 指派给自己。

  3. 新的主节点向集群广播一条 PONG 消息,这条 PONG 消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。

  4. 新的主节点开始接收处理槽有关的命令请求,故障转移完成。

选主流程和哨兵类似 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTE2MjQyNjc=,size_16,color_FFFFFF,t_70

 客户端如何定位数据所在实例

        Redis 实例会将自己的哈希槽信息通过 Gossip 协议发送给集群中其他的实例,实现了哈希槽分配信息的扩散。

        这样,集群中的每个实例都有所有哈希槽与实例之间的映射关系信息。

        当客户端连接任何一个实例,实例就将哈希槽与实例的映射关系响应给客户端,客户端就会将哈希槽与实例映射信息缓存在本地。

        当客户端请求时,会计算出键所对应的哈希槽,在通过本地缓存的哈希槽实例映射信息定位到数据所在实例上,再将请求发送给对应的实例。

重定向机制

        客户端将请求发送到实例上,这个实例没有相应的数据,该 Redis 实例会告诉客户端将请求发送到其他的实例上。

        MOVED 错误(负载均衡,数据已经迁移到其他实例上):当客户端将一个键值对操作请求发送给某个实例,而这个键所在的槽并非由自己负责的时候,该实例会返回一个 MOVED 错误指引转向正在负责该槽的节点。

        ASK错误:槽部分迁移未完成的情况下,如果需要访问的 key 所在 Slot 正在从从 实例 1 迁移到 实例 2,实例 1 会返回客户端一条 ASK 报错信息。

集群节点上限

        Redis 官方给的 Redis Cluster 的规模上线是 1000 个实例。

        关键在于实例间的通信开销,Cluster 集群中的每个实例都保存所有哈希槽与实例对应关系信息(Slot 映射到节点的表),以及自身的状态信息。

redis高级数据结构应用 Bitmap 实现亿级海量数据统计

四种统计类型:二值统计、聚合统计、排序统计和基数统计

而二值统计,大数据量的情况下,非常适合使用redis的bitmap。

 

        Bitmap 的底层数据结构用的是 String 类型的 SDS 数据结构来保存位数组,Redis 把每个字节数组的 8 个 bit 位利用起来,每个 bit 位 表示一个元素的二值状态(不是 0 就是 1)。

        可以将 Bitmap 看成是一个 bit 为单位的数组,数组的每个单元只能存储 0 或者 1,数组的下标在 Bitmap 中叫做 offset 偏移量。

        Bitmap 提供了 GETBIT、SETBIT 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。

判断用户登录状态

        只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 GETBIT判断对应的用户是否在线。50000 万 用户只需要 6 MB 的空间。

用户每个月的签到情况

        在签到统计中,每个用户每天的签到用 1 个 bit 位表示,一年的签到只需要 365 个 bit 位。一个月最多只有 31 天,只需要 31 个 bit 位即可。

连续签到用户总数

我们把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1。

key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。

一共有 7 个这样的 Bitmap,如果我们能对这 7 个 Bitmap 的对应的 bit 位做 『与』运算。

总结

本文主要参考微信公众号码哥字节中redis系列文章,大家想看更详细的总结,可以去看看。

 

  • 8
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值