hankviv / blog_issue

issue
2 stars 0 forks source link

redis相关 #39

Open hankviv opened 4 years ago

hankviv commented 4 years ago

字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串 列表对象list:ziplist、linkedlist 哈希对象hash:ziplist、hashtable 集合对象set:intset、hashtable 有序集合对象zset:ziplist、skiplist

hankviv commented 4 years ago

redis为什么这么快: 1.所有数据都是存储在内存中, 2.c语言实现,丰富的数据结构,在基础的数据结构上做了优化。 3.使用单线程,无上下文的切换成本 4.非阻塞的io复用模型。

hankviv commented 4 years ago

1)多路 I/O 复用模型

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。

采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。

hankviv commented 4 years ago

那么为什么Redis是单线程的? Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了,毕竟采用多线程会有很多麻烦! 多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度。 我们使用单线程的方式是无法发挥多核CPU 性能,不过我们可以通过在单机开多个Redis 实例来完善! 一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理(例如:持久化等操作 还是会使用子进程或者子线程来实现的)

hankviv commented 4 years ago

redis 6.0多线程: 因为网络IO的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗。 所以redis使用多线程来处理网络数据读写。执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。

hankviv commented 4 years ago

热点key/大key: 热点key请求过多,导致某个节点达到网卡物理极限,集群访问量倾斜,热点key过期可能会导致缓存雪奔。 1.评估热点key:(1)通过对业务了解,(2)代理层记录,(3)使用redis-cli -hotkeys命令(耗时长,不是实时)(4)monitor命令来监控 2.如何处理:(1)客户端做二级缓存。(2)备份热key,备份到集群中多个节点

大key类似。也是导致分片不均匀。可以使用拆分key来处理。

hankviv commented 4 years ago

缓存击穿:单个key并发访问过高,过期时导致所有请求直接打到db 1.加锁更新,单个线程更新,2.计算ttl,提前更新。 缓存穿透:缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在一样 加一层布隆过滤器,原理是(k个hash函数映射到同一个bitmap的k个点上)。 缓存雪崩:当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上 1.冷启动或者初期新增缓存设置随机失效时间,避免同时失效 2.热点数据均匀分布在不同节点。

hankviv commented 4 years ago

redis淘汰key的策略是: 惰性删除+定时删除

hankviv commented 4 years ago

持久化方式有哪些?有什么区别? RDB: RDB持久化可以手动执行也可以根据配置定期执行,它的作用是将某个时间点上的数据库状态保存到RDB文件中, RDB文件是一个压缩的二进制文件, 可以通过SAVE或者BGSAVE来生成RDB文件。 BGSAVE则是会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程。 AOF: AOF是通过保存redis服务器所执行的写命令来记录数据库状态的。 服务器执行完写命令之后,写命令将会被追加append到aof_buf缓冲区的末尾,在服务器每结束一个事件循环之前,将会调用flushAppendOnlyFile函数决定是否要将aof_buf的内容保存到AOF文件中,可以通过配置appendfsync来决定 appendfsync三个配置: always ## aof_buf内容写入并同步到AOF文件,安全但是效率差 everysec ##每秒同步一次,默认 no ## 同步时间由操作系统决定 和每秒类似。

hankviv commented 4 years ago

实现Redis的高可用?

hankviv commented 4 years ago

主从配置:

修改从节点配置相关: REPLICAOF host port image

从节点设置为只读 replica-read-only yes 也可以使用命令行来操作: 例如:127.0.0.1:6379> REPLICAOF 127.0.0.1 6380

取消复制: 127.0.0.1:12345> REPLICAOF no one OK

ROLE:查看服务器的角色

主服务器: image 数组的第一个元素是字符串 "master" ,它表示这个服务器的角色为主服务器。 数组的第二个元素是这个主服务器的复制偏移量(replication offset),它是一个整数,记录了主服务器目前向复制数据流发送的数据数量。 第三个元素为所有从服务器的列表,每条信息 第一个为从服务器的ip 第二个是从服务器端口,第三个是从服务器的偏移量,如果和主服务器的偏移量一致,代表它们的数据就是一致的。

从服务器: image 数组的第一个元素是字符串 "slave" ,它表示这个服务器的角色是从服务器。 数组的第二个元素和第三个元素记录了这个从服务器正在复制的主服务器的 IP 地址和端口号。 数组的第四个元素是主服务器与从服务器当前的连接状态,这个状态的值及其表示的意思如下: "none" :主从服务器尚未建立连接;"connect" :主从服务器正在握手;"connecting" :主从服务器成功建立了连接; "sync" :主从服务器正在进行数据同步;"connected" :主从服务器已经进入在线更新状态;"unknown" :主从服务器连接状态未知。 数组的第五个元素是从服务器当前的复制偏移量。

hankviv commented 4 years ago

主从同步 (数据同步)

1.完整同步

当一个 Redis 服务器接收到 REPLICAOF 命令,开始对另一个服务器进行复制的时候, 主从服务器会执行以下操作: 1主服务器执行 BGSAVE 命令,生成一个 RDB 文件,并缓存区存储执行bgsave之后执行的命令。 2.在 RDB 文件创建完毕之后,主服务器会通过套接字,将 RDB 文件传送给从服务器。 3.从服务器在接收完主服务器传送过来的 RDB 文件之后,就会载入这个 RDB 文件。 4.当从服务器完成 RDB 文件载入操作,并开始上线接受命令请求时,主服务器就会把之前储存在缓存区里面的所有写命令发送给从服务器执行。

当从服务器载入完 RDB 文件,并执行完主服务器储存在缓冲区里面的所有写命令之后,主从服务器两者包含的数据库数据将完全相同。

这个通过创建、传送并载入 RDB 文件来达成数据一致的步骤,我们称之为完整同步操作。每个从服务器在刚开始进行复制的时候,都需要与主服务器进行一次完整同步。

在线更新 为了让主从服务器的数据一致性可以保持下去,让它们一直拥有相同的数据,Redis 会对从服务器进行在线更新:

每当主服务器执行完一个写命令之后,它就会将相同的写命令又或者具有相同效果的写命令发送给从服务器执行。

从服务器只要接收并执行主服务器发来的写命令,就可以让自己的数据库重新与主服务器数据库保持一致。

主从不一致问题: 在主服务器执行完写命令之后,直到从服务器也执行完相同写命令的这段时间里,主从服务器的数据库将出现短暂的不一致,因此要求强一致性的程序可能需要直接读取主服务器而不是读取从服务器。

第一个配置是和每个从节点成功ping时间,第二个配置是当前从节点数量 min-replicas-max-lag min-replicas-to-write 这两个配置代表着: 主服务器只有在 存活至少n台从服务器,且m秒内通讯成功时,才会执行写命令。 通过使用这两个配置选项,我们可以让主服务器只在主从服务器连接良好的情况下执行写命令。

当故障下线的从服务器重新上线时,主从服务器的数据通常已经不再一致,重新全量同步的话,比较费时间(sync), 现在优化为PSYNC,每个节点有自己的偏移量,二次同步的时候就从队列里面找当前的偏移量。 当redis节点成为一个master节点后,就会维护一个先进先出的队列,它会把每个被执行的写命令都记录到一个特定长度的先进先出队列里面。当一个节点重新上线的时候,就可以看丢失的段是否还在这个list里面。Redis 为这个队列设置的默认大小为 1 MB ,用户也可以根据自己的需要,通过配置选项 repl-backlog-size 来修改这个队列的大小。

从服务器可写 replica-read-only <yes|no> 可能会导致操作冲突,因为是命令类型的执行,但是也只是覆盖掉值。

心跳检测: 在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令。 REPLCONF ACK 其中 offset是从服务器当前的偏移量。 作用: 1..检测主从服务器的网络连接状态。 2.用于min-replicas-max-lag ,min-replicas-to-write 这两个参数的标定。 3.检测命令是否丢失,如果偏移量不一致,就有可能某次同步命令是否丢失。

hankviv commented 4 years ago

Redis 服务器拥有两种不同的脚本复制模式, 第一种是从 Redis 2.6 版本开始支持的脚本传播模式(whole script replication) 而另一种则是从 Redis 3.2 版本开始支持的命令传播模式(script effect replication)

hankviv commented 4 years ago

Redis Sentinel(哨兵) 基于主从方案的缺点还是很明显的,假设master宕机,那么就不能写入数据,那么slave也就失去了作用,整个架构就不可用了 除非你手动切换,主要原因就是因为没有自动故障转移机制。 而哨兵(sentinel)的功能比单纯的主从架构全面的多了,它具备自动故障转移、集群监控、消息通知等功能。

image

哨兵可以同时监视多个主从服务器,并且在被监视的master下线时,自动将某个slave提升为master,然后由新的master继续接收命令。整个过程如下:

2、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 的主观下线状态就会被移除。 sentinel会每隔1秒向所有实例(包括主从服务器和其他sentinel)发送ping命令,并且根据回复判断是否已经下线,这种方式叫做主观下线。当判断为主观下线时,就会向其他监视的sentinel询问,如果超过半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。

操作配置: /etc/sentinel.conf 修改sentinel.conf配置文件,从节点会使用role命令自动拉取

哨兵监控的master 主节点 名称 IP 端口号 选举次数

选举次数一般建议配置为 Sentinel 节点的一半加 1,就意思有几个哨兵认为master确实挂了才去切换。

sentinel monitor mymast 127.0.0.1 6379 1

master或者slave多少时间(默认30秒)不能使用标记为down状态。

sentinel down-after-milliseconds mymaster 5000

启动哨兵模式 ./redis-server /etc/sentinel.conf --sentinel &

哨兵监控:

hankviv commented 4 years ago

Redis Cluster 集群的原理:

为什么要实现Redis Cluster

1.主从复制中单机的QPS可能无法满足业务需求 2.现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上 3.网络流量需求:业务的流量已经超过服务器的网卡的上限值,可以考虑使用分布式来进行分流 集群通过数据分片sharding来进行数据的共享,同时提供复制和故障转移的功能。

分布方式:

1.节点取余分区: 优点:客户端分片,配置简单:对数据进行哈希,然后取余 缺点:数据节点伸缩时,导致数据迁移,迁移数量和添加节点数据有关,建议翻倍扩容(翻倍的话,迁移数据可以少点)

2.一致性哈希分区: 将所有的数据当做一个hash环,hash环中的数据范围是0到2的32次方。然后为每一个数据节点分配一个hash范围值,这个节点就负责保存这个范围内的数据。 key进行hash运算,被哈希后的结果在哪个hash的范围内,则按顺时针去找最近的节点,这个key将会被保存在这个节点上。 image 有4个key被hash之后的值在在n1节点和n2节点之间,按照顺时针规则,这4个key都会被保存在n2节点上, 如果在n1节点和n2节点之间添加n5节点,当下次有key被hash之后的值在n1节点和n5节点之间,这些key就会被保存在n5节点上面了 添加n5节点之后,数据迁移会在n1节点和n2节点之间进行,n3节点和n4节点不受影响,数据迁移范围被缩小很多 优点是:只影响临近的节点 缺点:还是有数据迁移

3.虚拟槽分区 虚拟槽分区是Redis Cluster采用的分区方式。 image 预设虚拟槽,每个槽就相当于一个数字,有一定范围。每个槽映射一个数据子集,一般比节点数大 Redis Cluster中预设虚拟槽的范围为0到16383 步骤: 1.把16384槽按照节点数量进行平均分配,由节点进行管理 2.对每个key按照CRC16规则进行hash运算 3.把hash结果对16383进行取余 4.把余数发送给Redis节点 5.节点接收到数据,验证是否在自己管理的槽编号的范围 如果在自己管理的槽编号范围内,则把数据保存到数据槽中,然后返回执行结果 如果在自己管理的槽编号范围外,则会把数据发送给正确的节点,由正确的节点来把数据保存在对应的槽中。 Redis Cluster的节点之间会共享消息,每个节点都会知道是哪个节点负责哪个范围内的数据槽 虚拟槽分布方式中,由于每个节点管理一部分数据槽,数据保存到数据槽中。当节点扩容或者缩容时,对数据槽进行重新分配迁移即可,数据不会丢失。

3.Redis Cluster基本架构 3.1 节点 Redis Cluster是分布式架构:即Redis Cluster中有多个节点,每个节点都负责进行数据读写操作 每个节点之间会进行通信。 3.2 meet操作,节点之间会相互通信 image

meet操作是节点之间完成相互通信的基础,meet操作有一定的频率和规则 image image

1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系 2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16383取余,计算自己的槽和对应节点 3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端 4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常 5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息 6.客户端向目标节点发送命令,获取命令执行结果

故障转移 如果节点A向节点B发送ping消息,节点B没有在规定的时间内响应pong,那么节点A会标记节点B为pfail疑似下线状态,同时把B的状态通过消息的形式发送给其他节点。 如果超过半数以上的节点都标记B为pfail状态,B就会被标记为fail下线状态,此时将会发生故障转移,优先从复制数据较多的从节点选择一个成为主节点,并且接管下线节点的slot,整个过程和哨兵非常类似,都是基于Raft协议做选举。

hankviv commented 4 years ago

Redis事务机制 1.multi——开启事务 通过multi命令开启事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行,而是被放到一个队列中,当 EXEC命令被调用时, 所有队列中的命令才会被执行。 exec——执行事务 当客户端向服务器发送EXEC命令时,会立刻执行。 DISCARD——放弃执行 当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空,并且客户端会从事务状态中退出。

watch命令 watch命令是一个乐观锁,可以为Redis事务提供 check-and-set (CAS)行为。它可以在EXEC命令执行前监视任意数量的键(key),在执行EXEC命令时,检查被监视的键是否已经被修改过,一旦发现被监视的键至少有一个被修改,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复(nil-reply)。

执行EXEC命令后会取消对所有键的监控,如果不想执行事务中的命令可以使用UNWATCH命令来取消监控。

如果queue里面有语法错误的话,输入exec的时候会直接报错,但是如果是执行错误的话,就会一直执行下去。