redis

string的数据结构为动态字符串(simple dynamic string)是可以修改的字符串,内部结构类似于java中的array list,采用预分配冗余空间的方式来减少内存的频繁分配。

sortedset 数据结构跳跃表

2.2 Redis 的三种特殊数据类型

  • Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
  • HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。
  • Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组

事务 redis事务没有隔离级别的概念,不保证原子性,开启事务后如有运行时异常,其他命令正常执行;如有语法类型异常全部不会执行

watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,
				被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 	标记一个事务块的开始( queued )
exec : 		执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 
discard : 	取消事务,放弃事务块中的所有命令
unwatch :	取消watch对所有key的监控

I/O 多路复用

多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

什么是I/O多路复用?

  • I/O :网络 I/O
  • 多路 :多个网络连接
  • 复用:复用同一个线程。
  • IO多路复用其实就是一种同步IO模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出cpu。

哨兵 故障切换的过程是怎样的呢

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时(可配置),那么哨兵之间就会进行一次投票,其他哨兵在没有同意别的哨兵的leader请求时,就会把票投给该sentinel,投票的结果由一个哨兵发起,进行 failover 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。

- 故障master剔除,从slave中挑选一个成为master。遵照的原则如下:

  • slave的优先级

  • slave从master那同步的数据量,那个slave多就优先。

14.1 延时双删 先删库再删缓存 然后间隔一段时间再删缓存 如果失败,删除缓存重试机制 可以放消息队列

redis集群模式性能优化

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

redLock

多节点redis实现的分布式锁算法(RedLock):有效防止单点故障

假设有5个完全独立的redis主服务器

1.获取当前时间戳

2.client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

   比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

3.client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功

4.如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);

5.如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁

redission分布式锁 

图片

docker启动redis程序连接异常,映射本地配置文件启动

docker run -p 6379:6379 --name myredis -v /Users/hujinjin/Downloads/redis-7.0.4/redis.conf:/etc/redis/redis.conf -d redis redis-server

rdb默认开启 生成在根目录

aof手动开启 

RDB优缺点

  • 优点RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;Redis加载RDB文件恢复数据要远远快于AOF方式;
  • 缺点RDB方式实时性不够,无法做到秒级的持久化;每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;版本兼容RDB文件问题;

RDB和AOF混合方式(4.0版本)

Redis 4.0 中提出了一个 混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。

主从复制

psync增量同步

redis2.8之前的版本中断线情况下SYNC进行全量同步的低效问题,在Redis 2.8之后使用psync命令代替sync命令执行同步操作,psync具备了数据全量重同步 和 部分重同步模式

  • 全量重同步:跟redis2.8之前的版本一样全量复制。
  • 部分重同步:salve断开又重新连时,在命令传播阶段,只需要发送与master断开这段时间执行的写命给slave即可,可以理解为增量同步

PSYNC执行过程中比较重要的概念有3个:runid、offset(复制偏移量)以及复制积压缓冲区。

1.runid
每个Redis服务器都会有一个表明自己身份的ID。在PSYNC中发送的这个ID是指之前连接的Master的ID,如果没保存这个ID,PSYNC的命令会使用”PSYNC ? -1” 这种形式发送给Master,表示需要全量复制。
2.offset(复制偏移量)
在主从复制的Master和Slave双方都会各自维持一个offset。Master成功发送N个字节的命令后会将Master里的offset加上N,Slave在接收到N个字节命令后同样会将Slave里的offset增加N。
Master和Slave如果状态是一致的那么它的的offset也应该是一致的。
3.复制积压缓冲区
复制积压缓冲区是由Master维护的一个固定长度环形积压队列(FIFO队列),它的作用是缓存已经传播出去的命令。当Master进行命令传播时,不仅将命令发送给所有Slave,还会将命令写入到复制积压缓冲区里面。

docker主从模式 哨兵模式

https://www.jb51.net/article/164448.htm

linux命令 %s/vivian/sky/ 替换每一行的第一个 vivian 为 sky

优化

集中过期

如果你发现,平时在操作 Redis 时,并没有延迟很大的情况发生,但在某个时间点突然出现一波延时,其现象表现为:变慢的时间点很有规律,例如某个整点,或者每间隔多久就会发生一波延迟。

实例内存达到上限

如果你的 Redis 实例设置了内存上限 maxmemory,那么也有可能导致 Redis 变慢。

fork耗时严重

为了保证 Redis 数据的安全性,我们可能会开启后台定时 RDB 和 AOF rewrite 功能

你可以在 Redis 上执行 INFO 命令,查看 latest_fork_usec 项,单位微秒。

# 上一次 fork 耗时,单位微秒
latest_fork_usec:59477

开启AOF

前面我们分析了 RDB 和 AOF rewrite 对 Redis 性能的影响,主要关注点在 fork 上。

我总结了以下几种情况,你可以参考进行问题排查:

  1. 子进程正在执行 AOF rewrite,这个过程会占用大量的磁盘 IO 资源

  2. 有其他应用程序在执行大量的写文件操作,也会占用磁盘 IO 资源

这相当于在 AOF rewrite 期间,临时把 appendfsync 设置为了 none,配置如下:

# AOF rewrite 期间,AOF 后台子线程不进行刷盘操作
# 相当于在这期间,临时把 appendfsync 设置为了 none
no-appendfsync-on-rewrite yes

集群模式

docker run -d --name redis-node-1 -p 6379:6379 --net host --privileged=true -v /Users/hujinjin/Downloads/redis/redis-node1/redis.conf:/etc/redis/redis.conf redis:latest  --cluster-enabled yes --appendonly yes

映射本地配置文件关闭bind 127.0.0.1不然只能本机访问 关闭保护模式

集群添加新结点

redis-cli --cluster add-node 172.17.0.9:6379 172.17.0.7:6379 9新结点 7老结点,必须通过集群中已有的老结点带新结点加入集群

如果想把某结点变为新结点的slave,前提新结点还未分配slots

redis-cli -p 6379 cluster replicate 6f0ca4fe68e03c5cec899826eab3ae32c0a7acbc 主结点id

# 重新分派槽号

# 命令:redis-cli --cluster reshard IP地址:端口号

redis-cli --cluster reshard 192.168.202.200:6381

# 4096 下面的是接收hash槽的redis容器ID,这里是6387对应的id

# source node :从哪些原来的主机上分配资源下来,all表示全部master

参考

https://www.jb51.net/article/259571.htm

 緩存穿透

不存在key由于redis没有 大量请求到数据库

缓存击穿

 热门key过期引发大量请求到数据库

 缓存雪崩 大量key同时过期

如何做延迟队列、锁

三、为什么Redis集群的最大槽数是16384个?


2^14^=16384、2^16^=65536。

如果槽位是65536个,发送心跳信息的消息头是65536/8/1024 = 8k。

如果槽位是16384个,发送心跳信息的消息头是16384/8/1024 = 2k。

因为Redis每秒都会发送一定数量的心跳包,如果消息头是8k,未免有些太大了,浪费网络资源。

上面提过,Redis的集群主节点数量一般不会超过1000个。集群中节点越多,心跳包的消息体内的数据就越多,如果节点过多,也会造成网络拥堵。因此Redis的作者Salvatore Sanfilippo不建议Redis Cluster的节点超过1000个,对于节点数在1000个以内的Redis Cluster,16384个槽位完全够用。

Redis主节点的哈希槽信息是通过bitmap存储的,在传输过程中,会对bitmap进行压缩,bitmap的填充率越低,压缩率越高。

bitmap 填充率 = slots / N (N表示节点数)。

也就是说slots越小,填充率就会越小,压缩率就会越高,传输效率就会越高。
 

reactor模式

前面我们介绍的IO多路复用是操作系统的底层实现,借助IO多路复用我们实现了一个线程就可以处理大量网络IO请求,那么接收到这些请求后该如何高效的响应,这就是reactor要关注的事情,reactor模式是基于事件的一种设计模式。在reactor中分为3中角色:
Reactor:负责监听和分发事件
Acceptor:负责处理连接事件
Handler:负责处理请求,读取数据,写回数据

从线程角度出发,reactor又可以分为单reactor单线程,单reactor多线程,多reactor多线程3种。

单reactor单线程


处理过程:reactor负责监听连接事件,当有连接到来时,通过acceptor处理连接,得到建立好的socket对象,reactor监听scoket对象的读写事件,读写事件触发时,交由handler处理,handler负责读取请求内容,处理请求内容,响应数据。
可以看到这种模式比较简单,读取请求数据,处理请求内容,响应数据都是在一个线程内完成的,如果整个过程响应都比较快,可以获得比较好的结果。缺点是请求都在一个线程内完成,无法发挥多核cpu的优势,如果处理请求内容这一块比较慢,就会影响整体性能。

redis网络IO模型

redis网络IO模型底层使用IO多路复用,通过reactor模式实现的,在redis 6.0以前属于单reactor单线程模式。如图:

在linux下,IO多路复用程序使用epoll实现,负责监听服务端连接、socket的读取、写入事件,然后将事件丢到事件队列,由事件分发器对事件进行分发,事件分发器会根据事件类型,分发给对应的事件处理器进行处理。我们以一个get key简单命令为例,一次完整的请求如下:


请求首先要建立TCP连接(TCP3次握手),过程如下:
redis服务启动,主线程运行,监听指定的端口,将连接事件绑定命令应答处理器。
客户端请求建立连接,连接事件触发,IO多路复用程序将连接事件丢入事件队列,事件分发器将连接事件交由命令应答处理器处理。
命令应答处理器创建socket对象,将ae_readable事件和命令请求处理器关联,交由IO多路复用程序监听。

连接建立后,就开始执行get key请求了。如下:
 

reids 6.0以前网络IO的读写和请求的处理都在一个线程完成,尽管redis在请求处理基于内存处理很快,不会称为系统瓶颈,但随着请求数的增加,网络读写这一块存在优化空间所以redis 6.0开始对网络IO读写提供多线程支持。需要知道的是,redis 6.0对多线程的默认是不开启的,可以通过 io-threads 4 参数开启对网络写数据多线程支持,如果对于读也要开启多线程需要额外设置 io-threads-do-reads yes 参数,该参数默认是no,因为redis认为对于读开启多线程帮助不大,但如果你通过压测后发现有明显帮助,则可以开启。

redis 6.0多线程模型思想上类似单reactor多线程和多reactor多线程,但不完全一样,这两者handler对于逻辑处理这一块都是使用线程池,而redis命令执行依旧保持单线程。如下:

可以看到对于网络的读写都是提交给线程池去执行,充分利用了cpu多核优势,这样主线程可以继续处理其它请求了。
开启多线程后多redis进行压测结果可以参考这里,如下图可以看到,对于简单命令qps可以达到20w左右,相比单线程有一倍的提升,性能提升效果明显,对于生产环境如果大家使用了新版本的redis,现在7.0也出来了,建议开启多线程。

网络抖动:为了解决网络抖动导致的集群节点之间突然不可访问,然后很快又恢复正常的情况,cluster提供了一个配置 cluster-node-timeout ,表示当某个节点持续timeout时间失联时才认定该节点故障然后进行选举新的主节点。

redis cluster集群选举原理:当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master ,由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,过程如下:

1、salve发现自己的master变为了FAIL

2、将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息

3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack

4、尝试failover的salve收集master返回的FAILOVER_AUTH_ACK

5、salve收到超半数master的ack后变成新的master(这也是集群需要至少三个主节点原因)

6、salve成为master后广播消息通知其他节点

注意:从节点并不是在主节点一进入FAIL状态就马上尝试进行选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,salve如果立即尝试选举的话其他master或许尚未意识到FAIL状态,可能拒绝投票。

延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方 式下,持有最新数据的slave将会首先发起选举(理论上)。

集群脑裂数据丢失问题:redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,这时会有大量数据丢失。

规避方法可以在redis配置里加上参数 min‐replicas‐to‐write 1 表示写数据成功最少同步的slave的数量,当然这个配置会一定程度上影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能提供服务了,需要根据场景权衡。

集群是否完整才能对外提供服务:当redis.conf配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。
 

全量同步和增量同步概述:
        全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。

        增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步?
        slave节点第一次连接master节点时

        slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步?
        slave节点断开又恢复,并且在repl_baklog中能找到offset时

rel_baklog:可以理解为是一个数据存储的集合

  offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。 如果slave的offset小于master的offset,说明slave数据落后于master,需要更新

  replid:当前master节点的唯一标识,在同步前确保该slave节点对应的master节点的同一个slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值