赞
踩
C写的开源高性能非关系型键值对数据库。底层采取epoll读写速度非常快,大多用于缓存,也提供了事务、持久化、集群以及多种数据类型的功能。
优点:
缺点:
guava/map是基于jvm内存的本地缓存,生命周期会随着jvm的结束而停止,也不存在持久化的特性。Redis是分布式缓存,可以进行持久化。
redis 内部使用文件事件处理器 file event handler,它是单线程的,所以redis才叫做单线程模型。它采用IO多路复用机制同时监听多个 socket。多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
答案来源,如有冒犯请及时联系:https://www.cnblogs.com/mrmirror/p/13587311.html
建立连接
执行一个set请求
答案来源,如有冒犯请及时联系:https://www.cnblogs.com/mrmirror/p/13587311.html
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
rdb(默认) 和aof两种。
rdb优点:
rdb缺点:
aof优点:
aof缺点:
fork子进程+copyonwrite技术。
fork()是unix和linux这种操作系统的一个api,而不是Redis的api。fork()用于创建一个子进程,注意是子进程,不是子线程。fork()出来的进程共享其父类的内存数据。仅仅是共享fork()出子进程的那一刻的内存数据,后期主进程修改数据对子进程不可见,同理,子进程修改的数据对主进程也不可见。比如:A进程fork()了一个子进程B,那么A进程就称之为主进程,这时候主进程A和子进程B所指向的内存空间是同一个,所以他们的数据一致。但是A修改了内存上的一条数据,这时候B是看不到的,A新增一条数据,删除一条数据,B都是看不到的。而且子进程B出问题了,对我主进程A完全没影响,我依然可以对外提供服务,但是主进程挂了,子进程也必须跟随一起挂。这一点有点像守护线程的概念。Redis正是巧妙的运用了fork()这个牛逼的api来完成RDB的持久化操作。
4.1.1、Redis中的fork()
Redis巧妙的运用了fork()。当bgsave执行时,Redis主进程会判断当前是否有fork()出来的子进程,若有则忽略,若没有则会fork()出一个子进程来执行rdb文件持久化的工作,子进程与Redis主进程共享同一份内存空间,所以子进程可以搞他的rdb文件持久化工作,主进程又能继续他的对外提供服务,二者互不影响。我们说了他们之后的修改内存数据对彼此不可见,但是明明指向的都是同一块内存空间,这是咋搞得?肯定不可能是fork()出来子进程后顺带复制了一份数据出来,如果是这样的话比如我有4g内存,那么其实最大有限空间是2g,我要给rdb留出一半空间来,扯淡一样!那他咋做的?采取了copyonwrite技术。
主进程fork()子进程之后,内核把主进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向主进程。这也就是共享了主进程的内存,当其中某个进程写内存时(这里肯定是主进程写,因为子进程只负责rdb文件持久化工作,不参与客户端的请求),CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入内核的一个中断例程。中断例程中,内核就会把触发的异常的页复制一份(这里仅仅复制异常页,也就是所修改的那个数据页,而不是内存中的全部数据),于是主子进程各自持有独立的一份。
数据修改之前的样子
数据修改之后的样子
其实就是更改数据的之前进行copy一份更改数据的数据页出来,比如主进程收到了set k 1
请求(之前k的值是2),然后这同时又有子进程在rdb持久化,那么主进程就会把k这个key的数据页拷贝一份,并且主进程中k这个指针指向新拷贝出来的数据页地址上,然后进行更改值为1的操作,这个主进程k元素地址引用的新拷贝出来的地址,而子进程引用的内存数据k还是修改之前的。
4.2.1、一句话总结copyonwrite
copyonwritefork()出来的子进程共享主进程的物理空间,当主子进程有内存写入操作时,read-only内存页发生中断,将触发的异常的内存页复制一份(其余的页还是共享主进程的)。
1.假设是全量复制,那么内存空间直接减半,浪费资源不说,数据量10g,全量复制这10g的时间也够长的。这谁顶得住?
2.如果不全量复制,会是怎样?相当于我一边复制,你一边写数据,看着貌似问题不大,其实不然。比如现在Redis里有k1的值是1,k2的值是2,比如bgsave了,这时候rdb写入了k1的值,在写k2的值之前时,有个客户端请求
set k1 11 set k2 22
那么持久化进去的是k2 22,但是k1的值还是1,而不是最新的11,所以会造成数据问题,所以采取了copyonwrite技术来保证触发bgsave请求的时候无论你怎么更改,都对我rdb文件的数据持久化不会造成任何影响。
就是每次都在aof文件后面追加命令。他与主进程收到请求、处理请求是串行化的,而非异步并行的。图示如下:
所以aof的频率高的话绝逼会对Redis带来性能影响,因为每次都是刷盘操作。跟mysql一样了。Redis每次都是先将命令放到缓冲区,然后根据具体策略(每秒/每条指令/缓冲区满)进行刷盘操作。如果配置的always,那么就是典型阻塞,如果是sec,每秒的话,那么会开一个同步线程去每秒进行刷盘操作,对主线程影响稍小。
其实Redis每次在写入AOF缓冲区之前,他都会调用flushAppendOnlyFile(),判断是否需要将AOF缓冲区的内容写入和同步到AOF文件中。这个决策是由配置文件的三个策略来控制的
1、分析
这道题不严谨,因为不知道他问的是持久化过程哪个快还是说Redis主进程对外提供请求哪个快?(也就是说哪个持久化方式会对主进程影响较大)
2、持久化过程哪个快
那肯定AOF,因为AOF每次只追加命令,而RDB每次都是全量覆盖。
3、哪个持久化方式会对主进程影响较大
那肯定是AOF效率低,因为AOF是串行的,相当于每次处理完命令都要同步的写入磁盘(当然也看具体策略),而bgsave模式的RDB则是开启子进程并行的处理这件事,不影响主进程对外提供请求,即使AOF策略开到最容易丢失数据的那种,那也是不定期磁盘操作,这是毫秒级别的。而RDB持久化即使发生CopyOnWrite也只是寻址操作,纳秒级别的。
只能同时存在一个,原因如下:
不能!
1.如果bgsave正在执行,那么client发送的bgrewriteaof命令会被推迟到bgsave结束后才得到执行。 2.如果bgrewriteaof正在执行,那么client发送的bgsave命令将会被服务器拒绝
RDB:
AOF:
比如我有业务很简单,就来回delete set 同一个key。就这个业务运行了10年,那么aof文件将记录无数个delete k1, set k1 xxx。其实都是重复的,但是我aof每次都追加,文件变成了1T大小。这时候Redis宕机了,要恢复,你想想1TB大小的aof文件去恢复,累死了。最主要的是1TB大小只记录了两个命令,所以压缩其实就是来处理这件事的。
Redis4.0之前就是将aof文件中重复的命令给去掉。保留最新的命令。进而减少aof文件大小。
4.0之前的做法效率很是低下,需要逐条命令对比。4.0开始的rewrite支持混合模式(也是就是rdb和aof一起用),直接将rdb持久化的方式来操作将二进制内容覆盖到aof文件中(rdb是二进制,所以很小),然后再有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite(还是按照rdb持久化的方式将内容覆盖到aof中)。但是这种模式也是配置的,默认是开,也可以关闭。
执行bgrewriteaof命令。
通过以下两个配置协作触发
- auto-aof-rewrite-min-size
AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64mb。
- auto-aof-rewrite-percentage
当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。
混合持久化结合了RDB持久化 和 AOF 持久化的优点,采取了rdb的文件小易于灾难恢复,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。
兼容性差,一旦开启了混合持久化,在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,需要专业的工具来阅读,因为是二进制,所以阅读性较差。
Redis4.0以及以后新增的内容,很强大。默认开启,也建议开启。但具体还要看业务,比如业务就允许数据丢失,那直接RDB就完事了,不需要开AOF,这样效率还高。如果一点数据都不允许丢失,那只能RDB+always策略的AOF,换言之,不管怎样,都建议开启RDB,RDB可以应对灾难性快速恢复。混合持久化也不用过于担心文件大小问题,4.0开始的rewrite支持混合模式直接按rdb持久化的方式来操作将二进制内容覆盖到aof文件中(rdb是二进制,所以很小),然后再有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite(还是按照rdb持久化的方式将内容覆盖到aof中),堪称完美!
会优先看是否存在aof文件,若存在则先按照aof文件恢复,因为aof毕竟比rdb全。若aof不存在,则才会查找rdb是否存在。这是默认的机制。毕竟aof文件也rewrite成rdb二进制格式,文件小,易于回复。所以redis会优先采取aof。
同步、阻塞。
致命的问题,持久化的时候redis服务阻塞(准确的说会阻塞当前执行save命令的线程,但是redis是单线程的,所以整个服务会阻塞),不能继对外提供请求,GG!数据量小的话肯定影响不大,数据量大呢?每次复制需要1小时,那就相当于停机一小时。
异步、非阻塞。
采取fork() + copyonwrite的方式,他可以一边进行持久化,一边对外提供读写服务,互不影响,新写的数据对我持久化不会造成数据影响,你持久化的过程中报错或者耗时太久都对我当前对外提供请求的服务不会产生任何影响。持久化完会将新的rdb文件覆盖之前的。
如果假设你设置一个一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的? 答案是:定期删除+惰性删除
定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。
定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下
通过上述两种手段结合起来,保证过期的key一定会被干掉
很简单,就是说,你的过期key,靠定期删除没有被删除掉,还停留在内存里,占用着你的内存呢,除非你的系统去查一下那个key,才会被redis给删除掉
但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?
答案是:走内存淘汰机制。
如果redis的内存占用过多的时候,此时会进行内存淘汰策略: 假设如下场景:
redis里有10000个key,现在已经满了,redis需要触发内存淘汰策略删除key 1个key,最近1min被查了2w次 1个key,最近10min被查了100次 1个key,最近1小时被查了10次
总结:Redis的内存淘汰策略的选取并不会影响过期的key的处理,而是用于处理内存不足时需要申请额外空间的数据,过期策略用于处理过期的缓存数据。
Redis的事务并不像Mysql那么灵活,有隔离级别,出问题后还能回滚数据等高级操作。Redis毕竟是非关系型数据库,他目前事务回滚机制是不执行命令,也就是可以采取watch命令模拟乐观锁,进行监听数据,发现数据不是事务开始时候的样子了,那么我这个事务里的命令就不会得到执行。
Redis是单进程的且它保证在执行事务时不会对事务进行中断,事务可以从运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。他目前事务回滚机制是不执行命令,也就是可以采取watch命令模拟乐观锁,进行监听数据,发现数据不是事务开始时候的样子了,那么我这个事务里的命令就不会得到执行。
lua脚本。
watch
每个数据库都维护一个字典watched_keys,key为监听的key,value即为监听的客户端对象。
所有客户端的修改都会去检查这个字典,如果key一致则会去打开客户端对象的 REDIS_DIRTY_CAS.
multi 和 exec
multi命令将客户端标记为“事物状态”,这个客户端后续的普通操作指令都会放入事务队列(客户端会保存一个事务队列),直到执行multi、exec、watch(?)、discard。
exec时会服务端去查当前客户端的REDIS_DIRTY_CAS,如果被打开了,则拒绝客户端提交的事务。没打开则遍历客户端事务队列,挨条执行。
pipeline:
目的是优化request/response模型、降低等待时间和IO调用。一次请求发送多个命令,处理一条命令,就返回一次结果,其中可能会穿插其他命令。客户端缓存命令,达到发送条件或者缓冲慢了一并发出,然后处理应答。
事务
一次发送并执行,事务内不会穿插其他命令。客户端命令会被存储,然后exec发送全部执行。
采取主从复制,用slave来做从节点,读写分离。主节点挂了的话从节点可以切换为主继续提供工作。哨兵方式的话可以自动故障切换。
优点:数据强一致性。(但是会破坏可用性,也就是CAP的A)
缺点:效率低,同步阻塞。
优点:效率高,异步非阻塞。
缺点:会丢失数据,满足了CAP的A,舍弃了CAP的C。
优点:效率相对较高、能保证数据最终一致性。
缺点:没发现啥缺点。非要说缺点那就是有可能取到不一致的数据,因为不是强一致性。为什么Redis不采取这个?因为Redis要高效率,不想融入太多组件(MQ)进来。
当启动一个slave节点的时候他会发送 PSYNC命令给master节点
如果slave是第一次连接到master,那么会触发一次full resynchronization
进行全量复制。开始full resynchronization
的时候,master会启动一个后台线程负责生成rdb文件,与此同时还会将客户端收到的所有写命令缓存到内存中。rdb文件生成完成后,master会将这个rdb发送给slave,slave会先把master传来的rdb写入磁盘然后清空自己的旧数据然后 从本地磁盘load到内存中进行同步数据,然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。
若slave不是第一次连接到master,而是因为某种原因(比如网络故障)断开了进行重新链接的,那么master仅仅会复制给slave断开这段时间缺少的那部分数据,不会全量复制。这个称为断点续传。
master节点会在内存中创建一个backlog,然后master和slave都会保存一个replica offset和一个master run id,offset就是保存在backlog中的。 若master和slave的网络链接断开了,那么slave会让master从上次的replica offset开始继续复制。若没找到对应的offset,则进行一次全量复制。
redis2.8开始支持的主从复制断点续传,若主从复制过程中出现了网络故障导致网络链接断开了,那么slave重新连接到master的时候可以接着上次复制的地方继续复制下去,而不是从头开始复制。
master节点会在内存中创建一个backlog,然后master和slave都会保存一个replica offset和一个master id,offset就是保存在backlog中的。 若master和slave的网络链接断开了,那么slave会让master从上次的replica offset开始继续复制。若没找到对应的offset,则进行一次全量复制。
backlog是一个环形缓冲区,整个master进程中只会存在一个,所有的slave公用,默认大小是1MB,在master给slave复制数据的时候,也会将增量数据在backlog中同步写一份,backlog主要用于做全量复制中断的时候的增量复制的。
master和slave都会维护一个offset,master和slave都会不断累加offset,然后slave每秒都上报自己的offset给master,同时master也会保存每个slave的offset,然后slave上报offset给master的时候,master发现offset不一致就能发现数据不一致的情况了。
首先来说一下复制缓冲区。
作用:主节点开始和一个从节点进行全量同步时,会为从节点创建一个输出缓冲区,这个缓冲区就是复制缓冲区。当主节点向从节点发送 RDB 文件时,如果又接收到了写命令操作,就会把它们暂存在复制缓冲区中。等 RDB 文件传输完成,并且在从节点加载完成后,主节点再把复制缓冲区中的写命令发给从节点,进行同步。对主从同步的影响:如果主库传输 RDB 文件以及从库加载 RDB 文件耗时长,同时主库接收的写命令操作较多,就会导致复制缓冲区被写满而溢出。一旦溢出,主库就会关闭和从库的网络连接,重新开始全量同步。所以,我们可以通过调整 client-output-buffer-limit slave 这个配置项,来增加复制缓冲区的大小,以免复制缓冲区溢出。
再来看看复制积压缓冲区。
作用:主节点和从节点进行常规同步时,会把写命令也暂存在复制积压缓冲区中。如果从节点和主节点间发生了网络断连,等从节点再次连接后,可以从复制积压缓冲区中同步尚未复制的命令操作。对主从同步的影响:如果从节点和主节点间的网络断连时间过长,复制积压缓冲区可能被新写入的命令覆盖。此时,从节点就没有办法和主节点进行增量复制了,而是只能进行全量复制。针对这个问题,应对的方法是调大复制积压缓冲区的大小
repl_backlog_buffer 是主库为了从库断开重连,避免全量复制,找到主从差异而设计的缓冲区
replication buffer 是主库为了等待从库执行命令,降低发送次数,而设计的缓冲区
存储的内容都是相应时间点master更新的数据,具体时间点为:
这个值太小会导致主从断开,进而造成无限重传问题。
master在内存中直接创建rdb文件然后发送给slave,不会在本地落盘。此种方式不安全,因为没了持久化操作,但效率高,因为减少了一次磁盘IO操作,直接网络传输给slave。配置参数如下:
# 默认是no,先落盘,再进行网络传输。 repl-diskless-sync no # 等待一定时长再开始复制,因为要等更多slave重新连接过来 repl-diskless-sync-delay
首先slave是不会过期key的,只会等master过期key,若master过期了一个key或者lru淘汰了一个key,那么master会模拟一条del命令发送给slave。
有哨兵之前都是手动进行故障转移。sentinel可以将之前纯人工进行故障操作的步骤自动化。哨兵主要包含以下功能:
哨兵也需要单独部署,但是他不负责读写请求,只是个看门狗,负责监控Redis集群和自动故障转移。
是通过PSUBSCRIBE这个Redis内置的发布订阅命令来实现的,他们共同监听__sentinel__:hello
这个channel,每隔两秒钟,每个哨兵都会往自己监控的某个master+slaves对应的__sentinel__:hello channel
里发送一个消息,内容是自己的host、ip和runid还有对这个master的监控配置,每个哨兵也会去监听自己监控的每个master+slaves对应的__sentinel__:hello channel
,然后去感知到同样在监听这个master+slaves的其他哨兵的存在,每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步。
脑裂:某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master,这个时候,集群里就会有两个master,也就是所谓的脑裂。
解决方案
# 表示连接到master的最少slave数量 min-replicas-to-write 3 # 表示slave连接到master的最大延迟时间 min-replicas-max-lag 10
按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失(最多丢失10s)。
会造成,如下两个可能:
解决方案
# 表示连接到master的最少slave数量 min-replicas-to-write 3 # 表示slave连接到master的最大延迟时间 min-replicas-max-lag 10
按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失(最多丢失10s)。是的,没有根治,只是减少了丢失量。若要当作db来用,需要零丢失的话,可以引入mq当中间件。
sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机
odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机
sentinal内部选leader逻辑?
在 sentinal 操纵下,进行故障转移分为三大步骤。
细节:
基于raft协议,quorum数一般为(n/2)+1
,具体流程如下
sentinel发现master下线,修改其状态为sdown。
sentinel和其他sentinel确认master是否down掉,超过quorum指定的哨兵进程都认为sdown之后,就变为odown。
对我们的当前epoch进行自增, 并尝试在这个epoch中当选,也就是说首先发现master down掉的sentinel有优先权当选为leader。
如果当选失败,那么在设定的故障迁移超时时间的两倍之后,重新尝试当选。如果当选成功,那么执行以下步骤。
选出一个从服务器,并将它升级为主服务器。
leader选出一个slave作为master,发送slaveof no one命令(slaveof no one 顾名思义了,就是我不是任何人的slave)。
通过发布与订阅功能,将更新后的配置传播给所有其他Sentinel,其他Sentinel对它们自己的配置进行更新。
并通过给其他slave发送slaveof master命令告知其他slave新的master。
当所有从服务器都已经开始复制新的主服务器时,领头Sentinel终止这次故障迁移操作。
(n/2)+1
?我是这么理解的哈:
跟master断开连接的时长,如果一个slave跟master断开连接已经超过了down-after-milliseconds
配置的10倍,那么slave就被认为不适合选举为master
按照slave优先级进行排序,slave priority越低,优先级就越高
如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
如果上面两个条件都相同,那么选择一个run id比较小的那个slave
Redis集群并没有采取hash取模和一致性hash算法,而是采取的槽slot的方式进行分布数据的。Redis一共分为16384个slot,接收到命令后具体流程如下:
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
16384个。
cluster默认是不支持slave节点读或者写的,跟我们手动基于哨兵搭建的主从架构不一样的,需要手动带上readonly这个指令,这个时候才能在slave node进行读取。默认的话就是读和写都到master上去执行的,redis cluster模式下就不建议做物理的读写分离了,建议通过master的水平扩容,来横向扩展读写吞吐量,还有支撑更多的海量数据。
Redis集群的高可用并不像哨兵那样需要单独部署哨兵程序,默认也不会像主从复制那样读写分离,Redis集群的slave节点仅仅做数据备份以及高可用,比如现在有5个master,每个master都有1个slave,然后新增了3个slave作为冗余,那么现在有的master就有2个slave了,也就是有的master出现了salve冗余。如果某个master的slave挂了,那么redis cluster会自动迁移一个冗余的slave给那个master,所以集群部署的时候最好多冗余出几个slave做高可用。
或者通过链表方式进行配置:master<-slave<-slave ,这样master废了下一个slave可以顶上,其他slave不需要变动。
扩容:
缩容:
和哨兵的类似的流程。
cluster-node-timeout * cluster-slave-validity-factor
,那么就没有资格切换成master,这个也是跟哨兵是一样的,从节点超时过滤的步骤。master node(N/2 + 1)
都投票给了某个从节点,那么选举通过,那个从节点可以切换成master,从节点执行主备切换,从节点切换为主节点。redis内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略,热点数据不会被淘汰掉。
会进行内存淘汰机制,当Redis达到内存上限时会淘汰掉不活跃的数据。如果还是不够,内存还是满了的情况下Redis的写命令会返回错误信息,读命令还可以正常返回。
什么是epoll?这篇文章解释的淋漓尽致:
问题描述:多个系统操作一个Key,本来想将这个Key 的 value依次改为 1,2,3,4.
但是因为并发问题造成乱序,1、3、4、2,最终结果变为2.
解决:
分布式锁
主要是用 setnx 的命令,SET if Not eXists,如果存在则返回失败。
MQ串行化
setnx
setnx简单,但要注意:value要设置为线程或者请求的id等作为加锁者的唯一标识,因为setnx为了避免死锁是要设置超时时间的,如果线程A加锁之后,操作时间过长导致锁超时,这时线程B就能上锁了,但是就在这时,线程A工作完成,把锁给删了,这样就等于把线程B的锁删了。
加唯一标识可以解决这个问题,但这也引出一个问题,就是最后释放删除锁时需要判断唯一标识是否和当前加锁者匹配,但这两步并非原子操作,还是有可能确认匹配之后,锁超时然后线程B上锁,但是线程A又把线程B的锁给干掉了。
lua
lua脚本可以解决这个非原子删除解锁的问题。
Redisson
是redis的java客户端,它的很多api实现了分布式锁功能,实际原理基本都是对lua脚本的封装。
比如这里面就可以实现分布式锁的重入。redisson也对redlock有支持。
RedLock
主要关注就是主从问题,redis异步复制可能没及时把刚刚set的锁同步到从节点就挂了。
所以加锁成功的判定是,有n/2+1个节点加锁成功就判定成功,解锁通知全部节点。
但是锁有时间限制,判定加锁成功也有时间限制,还是会有问题(神仙打架事件)。
建议提前做好数据量预估然后选择合适的Redis集群数,避免后期在新增服务器的时候还需要重新分片。目前Redis集群无法做到动态自动扩容,需要手动执行命令重新reshard。
单机直接pass掉,不考虑,单点故障!
如果数据量不大,一般来说你的缓存的总量在10G以内就可以,那么建议按照以下架构去部署redis
redis持久化+备份方案+容灾方案+replication(主从+读写分离)+sentinal(哨兵集群,3个节点,高可用性)
可以支撑的数据量在10G以内,可以支撑的写QPS在几万左右,可以支撑的读QPS可以上10万以上(随你的需求,水平扩容slave节点就可以),可用性在99.99%。
如果你的数据量很大,海量数据,那么选择redis cluster方式部署,多master分布式存储数据,水平扩容,支撑更多的数据量,读写QPS分别都达到几十万都没问题,只要扩容master即可,redis cluster,读写分离,支持不太好,readonly才能去slave上读,支撑99.99%可用性,也没问题,slave -> master的主备切换,冗余slave去进一步提升可用性的方案(每个master挂一个slave,但是整个集群再加个3个slave冗余一下)
情况一
大批量热点数据过期后,高并发请求,直接请求到db,引起db压力造成查询阻塞甚至宕机。
解决方案
情况二
Redis挂了。导致请求都到db了。假设高并发5000个请求,本来缓存再高峰期可以抗住每秒4000个请求,但是Redis挂了,5000个请求直接干到了数据库层,这可能又把数据库给打死了。
解决方案
Redis做高可用,一主多从。
加一层本地ehcache缓存。也就是请求进来先查ehcache缓存,ehcache没有的话去查redis,redis也没有再去db。这时候redis挂了,但是我们本地ehcache还有,所以不会打到db层。
限流组件,可以设置每秒最大请求数,超过最大请求数的那些请求怎么办?走降级!可以返回一些默认值,或者友情提示啥的。
限流的好处是数据库绝对不会死,因为限流组件保证了有多少请求能进来。只要数据库不死,就能对外继续提供请求。虽然有部分用户请求走降级,但不是全部,会有大部分请求得到执行。
比如电商网站,有以下商品在redis里和mysql里。且Redis里只存放热点商品, 而不是全部。 苹果、香蕉、鸭梨等,但是用户搜了个我这电商网站里没有卖的商品,这时候redis里肯定没有搜的数据,就去请求db了。db也不一定有(有可能有有可能没有),如果没有的话那白白浪费性能了。这就是缓存穿透。量少的话就别说了,量少都没必要redis缓存。大量的话会很恐怖,比如淘宝,他无所不卖,万一真找到一个不卖的东西去搜,然后redis没有,结果都去请求mysql了。那不GG了嘛?
解决方案
布隆过滤器。原理就是利用bitmap来解决缓存穿透的一种技术手段。实现步骤如下:
某个 key 非常非常热点,访问非常的频繁,高并发访问的情况下,当这个 key在失效(可能expire过期了,也可能LRU淘汰了)的瞬间,大量的请求进来,这时候就击穿了缓存,直接请求到了数据库,一下子来这么多,数据库肯定受不了,这就叫缓存击穿。某个key突然失效,然后这时候高并发来访问这个key,结果缓存里没有,都跑到db了。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
缓存预热就是系统启动的时候就查询数据库将相关的数据直接缓存到Redis中。这样可以避免用户查询的时候先判断是否有缓存,没的话在查db进行缓存等一系列操作了。
实现方案
如果数据量不大的话,可以在项目启动的时候进行缓存。
如果数据量很大的话可以写个简易的页面,上线时手工点一下进行缓存。
定时任务定时刷新缓存。
就是当缓存出故障的时候,别影响主业务系统,让他可以继续对外提供服务。
实现方案
一般是三级缓存,redis属于第二层。
实现方式
热点key就是频繁被访问的key,为了防止频繁访问数据库,所以需要将这些数据缓存起来,这些数据就称为热点数据。
一般采取分布式锁来安全的缓存热点key,也就是对查询缓存加锁,即如果key不存在就加锁,然后查数据库set到缓存,然后解锁。其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。
强烈安利一篇文章!评论也很精彩哟!
https://zhuanlan.zhihu.com/p/59167071
总结一下:
缓存更新耗精力,不如一删了之爽;
先删缓存再写库,延迟双删保一致;
写库之后删缓存,读库有值写缓存;
并发问题概率小,再出问题找领导。
写库后删除缓存出现缓存和db数据不一致问题的概率就不大了,但是还有可能出现线程A读取数据但是延迟迟迟没有写缓存,线程B改库删缓存之后,线程A才写,那数据还是老的;或者线程B删缓存失败了。
解决可以引入MQ,对缓存的操作可以放在q里进行消费处理。
Redis的Pipeline类型,他会将一组命令用一次客户端服务端的请求来完成。
不适用场景:
适用场景:
比如群发短信、邮件这种,失败了我补偿,我不需要及时知道发送结果的,这种用pipeline合适 。
采取list数据类型,rpush生产消息,lpop消费消息,阻塞式的可以用blpop,在没有信息的时候,会一直阻塞,直到信息的到来。
采取zset数据类型,使用时间戳做score,然后用zadd来生产消息,消费组使用zrangbyscore
获取x秒之前的数据做轮询处理。
采取set数据类型的交集SINTER/SINTERSTORE
采取Redis的bitmap数据类型来存储,然后用Redis提供的bitmap命令进行位运算计算出连续登录天数,有多少用户活跃等。
不懂的看这篇:https://blog.csdn.net/ctwctw/article/details/105013817
选择key-value的原因:key-value简单粗暴,使用方便,效率更佳。 为什么不支持sql:因为redis的内存模型是一个hashtable,不使用表来存储数据,也不会预定义或强制要求用户对redis储存的不同数据进行关联。
redis中io多路复用器模块是单线程执行,事件处理器也是单线程执行,两个线程不一样。所以实际redis应该是单进程多线程,只是不同的模块都用的单线程实现。 两个维度来举例: (1)若是client发送命令到server的话,server处理命令是单线程逐条进行的。 (2)server内部可以是多线程的,比如aof持久化,假设策略每秒,那就是再单独开启一个线程去执行aof文件持久化操作,这就是多线程了。
参考redis各种部署方式的优缺点来决定。
前者用主从+哨兵进行高可用,加快读请求的速度,减轻单节点的压力。
后者用集群来均分这400G数据。
可以采取geo命令。3.2版本新增加的命令。主要原理是根据经纬度来计算距离。
存放慢查询语句的是一个先进先出的队列,他有固定长度,slowlog-max-len就是控制慢查询语句数量的,存放到内存中的,默认128,超过128的话会舍弃最先进入队列的命令。所以一般可以搞大点,因为他是存放到内存的,重启就没了。
慢查询阈值(单位:微妙,默认是10000,也就是10毫秒)。默认是超过10ms命令没执行完就扔到慢查询队列里(上面参数控制队列大小)。Redis号称QPS万级别,所以建议设置成1000,也就是1ms。
建议:
备份方案:
我们需要定时备份rdb文件来做冷备,为什么?不是有aof和rbd了吗为什么还要单独写定时任务去备份?因为Redis的aof和rdb是仅仅有一个最新的,比如谁手贱再Redis宕机的时候执行
rm -rf aof/rdb
了,那不就GG了吗?或者rdb/aof文件损坏了等不可预期的情况。所以我们需要单独备份rdb文件以防万一。为什么不定时备份aof而是rdb?定时备份aof没意义呀,定时本身就是冷备份,不是实时的,rdb文件又小恢复又快,她哪里不香?
恢复方案:
问题一:直接把备份的rdb扔到redis持久化目录下然后重启redis不行的原因在于:redis是按照先aof后rdb进行恢复的,所以都是开启aof的,redis启动后会重新生成新的aof文件,里面是空的。所以不会进行任何数据恢复,也就是说虽然你把rdb丢给redis了,但是redis会按照aof来恢复,而aof是redis启动的时候新生成的空文件,所以不会有任何数据进行恢复。
问题二:那么我们把rdb文件丢给redis后,先将redis的aof关闭再启动redis进程不就能按照rdb来进行恢复了吗?是这样的,没毛病!但是新的问题来了,我们aof肯定要开的,aof对数据保障更可靠。那什么我们按照rdb文件恢复完后再修改redis配置文件开启aof然后重启redis进程不就得了嘛?大哥…你打开aof然后重启redis,这时候redis又会生成一个空的aof文件,这时候恢复的时候又是啥数据都没了。因为数据是存到内存里,你重启后肯定没了,需要持久化文件来恢复。这时候aof是空的,我恢复个鸡毛啊。
具体方案:
我不管你是持久化文件丢了还是坏了,我都先
rm -rf *
给他删了。
- 停止redis进程
- 删除坏掉的rdb和aof持久化文件。
- 修改配置文件关闭redis的aof持久化。
- 找到最新备份的rdb文件扔到redis的持久化目录里。(这里最新的肯定是按照小时备份的最后一个)
- 启动Redis进程
- 执行
set appendonly yes
动态打开aof持久化。也就是说打开aof的操作不是修改配置文件然后重启,而是先热修改让他生成aof,这次生成肯定是会带着内存中完整的数据的。然后再修改配置文件重启。
- 等aof文件生成后再修改redis配置文件打开aof。
- 重启redis进程。
- 完美收官。
1、Redis的过期key删除策略有哪些?也是expire原理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。