redis高可用常见问题和解决方案

redis高可用常见问题和解决方案

redis持久化

RDB持久化方式:可以在指定的时间间隔能对数据进行快照存储.
AOF持久化方式:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.

如果服务器开启了AOF持久化功能。服务器会优先使用AOF文件还原数据。只有关闭了AOF持久化功能,服务器才会使用RDB文件还原数据
在这里插入图片描述
RDB的优点
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
基于RDB文件紧凑性,便于复制数据到一个远端数据中心,非常适用于灾难恢复.RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

RDB的缺点
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

AOF的优点:
使用AOF 会让你的Redis更加耐久:使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也可使用redis-check-aof工具修复问题.
Redis可以在AOF文件体积变得过大时,自动对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export)AOF 文件也非常简单(例如, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态)。

AOF 缺点:
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

如何选择使用哪种持久化方式?
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug 。

Redis并发竞争key的解决方案详解

问题::多客户端同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是由于并发设置的原因,最后顺序变成了4,3,2,最后变成的key值成了2。
在这里插入图片描述

解决Redis的并发竞争key问题

第一种方案:分布式锁
这种情况,主要是准备一个分布式锁,大家去抢锁,抢到锁就做set操作。

为什么是分布式锁?
因为传统的加锁的做法(如java的synchronized和Lock)这里没用,只适合单点。因为这是分布式环境,需要的是分布式锁。

当然,分布式锁可以基于很多种方式实现,比如zookeeper、redis等,不管哪种方式实现,基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

分布式锁的要求
互斥性:在任意一个时刻,只有一个客户端持有锁。
无死锁:即便持有锁的客户端崩溃或者其他意外事件,锁仍然可以被获取。
容错:只要大部分Redis节点都活着,客户端就可以获取和释放锁

分布式锁的实现方式
数据库
Memcached(add命令)
redis(setnx命令)
Zookeeper(临时节点)

第二种方案:利用消息队列
在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。
把Redis.set操作放在队列中使其串行化,必须的一个一个执行。这种方式在一些高并发的场景中算是一种通用的解决方案。

Redis缓存穿透,缓存击穿,缓存雪崩

缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从⽽可能压垮数据源。⽐如⽤⼀个不存在的⽤户id获取⽤户信息,不论缓存还是数据库都没有,若⿊客利⽤此漏洞进⾏攻击可能压垮数据库。

缓存击穿:key对应的数据存在,但在redis中过期,此时若有⼤量并发请求过来,这些请求发现缓存过期⼀般都会从后端DB加载数据并回设到缓存,这个时候⼤并发的请求可能会瞬间把后端DB压垮。

缓存雪崩:当缓存服务器重启或者⼤量缓存集中在某⼀个时间段失效,这样在失效的时候,也会给后端系统(⽐如DB)带来很⼤压⼒。(缓存大量失效,或者缓存整体不能提供服务)

1.缓存穿透解决⽅案
⼀个⼀定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写⼊缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
有很多种⽅法可以有效地解决缓存穿透问题,最常⻅的则是采⽤布隆过滤器,将所有可能存在的数据哈希到⼀个⾜够⼤的bitmap中,⼀个⼀定不存在的数据会被 这个bitmap拦截掉,从⽽避免了对底层存储系统的查询压⼒。另外也有⼀个更为简单粗暴的⽅法(我们采⽤的就是这种),如果⼀个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进⾏缓存,但它的过期时间会很短,最⻓不超过五分钟。

// 伪代码
public object GetProductListNew() {
	 int cacheTime = 30;
	 String cacheKey = "product_list";
	 String cacheValue = CacheHelper.Get(cacheKey);
	 if (cacheValue != null) {
	 	return cacheValue;
	 }
	 cacheValue = CacheHelper.Get(cacheKey);
	 if (cacheValue != null) {
	 	return cacheValue;
	 } else {
	 //数据库查询不到,为空
		 cacheValue = GetProductListFromDB();
		 if (cacheValue == null) {
		 //如果发现为空,设置个默认值,也缓存起来
		 cacheValue = string.Empty;
		 }
		 CacheHelper.Add(cacheKey, cacheValue, cacheTime);
		 return cacheValue;
	 }
}

2.缓存击穿解决⽅案
key可能会在某些时间点被超⾼并发地访问,是⼀种⾮常“热点”的数据。这个时候,需要考虑⼀个问题:缓存被“击穿”的问题。
使⽤互斥锁(mutex key)
业界⽐较常⽤的做法,是使⽤mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是⽴即去load db,⽽是先使⽤缓存⼯具的某些带成功操作返回值的操作(⽐如Redis的SETNX或者Memcache的ADD)去set⼀个mutex key,当操作返回成功时,再进⾏load db的操作并回设缓存;否则,就重试整个get缓存的⽅法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利⽤它来实现锁的效果。

// 伪代码
public String get(key) {
	 String value = redis.get(key);
	 if (value == null) { //代表缓存值过期
	 	//设置3min的超时,防⽌del操作失败的时候,下次缓存过期⼀直不能load db
	 	if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
			 value = db.get(key);
			 redis.set(key, value, expire_secs);
			 redis.del(key_mutex);
		 } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
			 sleep(50);
			 get(key); //重试
	 	}
	 } else {
	 	return value;
	 }
 }

3.缓存雪崩解决⽅案
缓存失效时的雪崩效应对底层系统的冲击⾮常可怕!⼤多数系统设计者考虑⽤加锁或者队列的⽅式保证来保证不会有⼤量的线程对数据库⼀次性进⾏读写,从⽽避免失效时⼤量的并发请求落到底层存储系统上。还有⼀个简单⽅案就时讲缓存失效时间分散开,⽐如我们可以在原有的失效时间基础上增加⼀个随机值,⽐如1-5分钟随机,这样每⼀个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
加锁排队只是为了减轻数据库的压⼒,并没有提⾼系统吞吐量。假设在⾼并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致⽤户等待超时,这是个治标不治本的⽅法!
注意:加锁排队的解决⽅式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,⽤户体验很差!因此,在真正的⾼并发场景下很少使⽤!

// 加锁排队伪代码
public object GetProductListNew() {
	 int cacheTime = 30;
	 String cacheKey = "product_list";
	 String lockKey = cacheKey;
	 String cacheValue = CacheHelper.get(cacheKey);
	 if (cacheValue != null) {
	 	return cacheValue;
	 } else {
		 synchronized(lockKey) {
			 cacheValue = CacheHelper.get(cacheKey);
			 if (cacheValue != null) {
			 	return cacheValue;
			 } else {
				 //这⾥⼀般是sql查询数据
				 cacheValue = GetProductListFromDB();
				 CacheHelper.Add(cacheKey, cacheValue, cacheTime);
			 }
		 }
		 return cacheValue;
	 }
}

1.保持缓存层的高可用性
使用Redis 哨兵模式或者Redis 集群部署方式,即便个别Redis 节点下线,整个缓存层依然可以使用。
除此之外,还可以在多个机房部署 Redis,这样即便是机房死机,依然可以实现缓存层的高可用。

2.限流降级组件
无论是缓存层还是存储层都会有出错的概率,可以将它们视为资源。作为并发量较大的分布式系统,假如有一个资源不可用,可能会造成所有线程在获取这个资源时异常,造成整个系统不可用。降级在高并发系统中是非常正常的,比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成整个推荐服务不可用。常见的限流降级组件如 Hystrix、Sentinel 等。

3.缓存不过期
Redis 中保存的 key 永不失效,这样就不会出现大量缓存同时失效的问题,但是随之而来的就是Redis需要更多的存储空间。

4.优化缓存过期时间
设计缓存时,为每一个 key 选择合适的过期时间,避免大量的 key 在同一时刻同时失效,造成缓存雪崩。

5.使用互斥锁重建缓存
在高并发场景下,为了避免大量的请求同时到达存储层查询数据、重建缓存,可以使用互斥锁控制,如根据 key 去缓存层查询数据,当缓存层为命中时,对 key 加锁,然后从存储层查询数据,将数据写入缓存层,最后释放锁。若其他线程发现获取锁失败,则让线程休眠一段时间后重试。对于锁的类型,如果是在单机环境下可以使用 Java 并发包下的 Lock,如果是在分布式环境下,可以使用分布式锁(Redis 中的 SETNX 方法)

Redis缓存和MySQL数据⼀致性的解决⽅案

采⽤延时双删策略保证缓存和mysql数据⼀致
异步更新缓存保证缓存和mysql数据⼀致

需求分析
在⾼并发的业务场景下,数据库⼤多数情况都是⽤户并发访问最薄弱的环节。所以,就需要使⽤redis做⼀个缓冲操作,让请求先访问到redis,⽽不是直接访问MySQL等数据库。
在这里插入图片描述
这个业务场景,主要是解决读数据从Redis缓存,⼀般都是按照下图的流程来进⾏业务操作。
在这里插入图片描述
读取缓存步骤⼀般没有什么问题,但是⼀旦涉及到数据更新:数据库和缓存更新,就容易出现缓存
(Redis)和数据库(MySQL)间的数据⼀致性问题

不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不⼀致的情况。举⼀个例⼦:
1.如果删除了缓存Redis,还没有来得及写库MySQL,另⼀个线程就来读取,发现缓存为空,则去数据库中读取数据写⼊缓存,此时缓存中为脏数据。
2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不⼀致情况。因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不⼀致的问题。如来解决?这⾥给出两个解决⽅案,先易后难,结合业务和技术代价选择使⽤。

缓存和数据库⼀致性解决⽅案

第⼀种⽅案:采⽤延时双删策略
在写库前后都进⾏redis.del(key)操作,并且设定合理的超时时间。

public void write(String key,Object data){
 redis.delKey(key);
 db.updateData(data);
 Thread.sleep(500);
 redis.delKey(key);
 }

1.具体的步骤
1)先删除缓存
2)再写数据库
3)休眠500毫秒
4)再次删除缓存
那么,这个500毫秒怎么确定的,具体该休眠多久呢?
需要评估⾃⼰的项⽬的读数据业务逻辑的耗时。这么做的⽬的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加⼏百ms即可。⽐如:休眠1秒。

2.设置缓存过期时间
从理论上来说,给缓存设置过期时间,是保证最终⼀致性的解决⽅案。所有的写操作以数据库为准,只要到达缓存过期时间,则后⾯的读请求⾃然会从数据库中读取新值然后回填缓存。

3.该⽅案的弊端
结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不⼀致,⽽且⼜增加了写请求的耗时。

第⼆种⽅案:异步更新缓存(基于订阅binlog的同步机制)
1.技术整体思路:
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

1)读Redis:热数据基本都在Redis
2)写MySQL:增删改都是操作MySQL
3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis

2.Redis更新
1)数据操作主要分为两⼤块:
⼀个是全量(将全部数据⼀次写⼊到redis)
⼀个是增量(实时更新)
这⾥说的是增量,指的是mysql的update、insert、delate变更数据。
2)读取binlog后分析 ,利⽤消息队列,推送更新各台的redis缓存数据。
这样⼀旦MySQL中产⽣了新的写⼊、更新、删除等操作,就可以把binlog相关的消息推送⾄Redis,Redis再根据binlog中的记录,对Redis进⾏更新。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据⼀致性。
这⾥可以结合使⽤canal(阿⾥的⼀款开源框架),通过该框架可以对MySQL的binlog进⾏订阅,⽽canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。当然,这⾥的消息推送⼯具你也可以采⽤别的第三⽅:kafka、rabbitMQ等来实现推送更新Redis。

Redis哨兵、复制、集群的设计原理,以及区别

  1. 哨兵(Sentinel):可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能。
  2. 复制(Replication):则是负责让一个Redis服务器可以配备多个备份的服务器。
    Redis正是利用这两个功能来保证Redis的高可用。

2.哨兵(sentinal)
1.Redis哨兵主要功能
(1)集群监控:负责监控Redis master和slave进程是否正常工作
(2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
(3)故障转移:如果master node挂掉了,会自动转移到slave node上
(4)配置中心:如果故障转移发生了,通知client客户端新的master地址
2.Redis哨兵的高可用
原理:当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可
用性。
在这里插入图片描述
哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。
同时哨兵节点之间也互相通信,交换对主从节点的监控状况。
每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。

这个就是哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线

  1. 主观下线:一个哨兵节点判定主节点down掉是主观下线。
  2. 客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。
  3. 原理:基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制
    Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。

3.Redis 复制(Replication)
Redis为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,实现Redis的高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性。

1.数据复制原理(执行步骤)
在这里插入图片描述

①从数据库向主数据库发送sync(数据同步)命令。
②主数据库接收同步命令后,会保存快照,创建一个RDB文件。
③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
④主数据库将缓冲区的所有写命令发给从服务器执行。
⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。
注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制。
在这里插入图片描述

4.Redis 主从复制、哨兵和集群这三个有什么区别

  1. 主从模式:读写分离,备份,一个Master可以有多个Slaves。
  2. 哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。
  3. 集群:为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性

Redis为什么是单线程、及⾼并发快的原因

Redis的⾼并发和快速原因
  1. redis是基于内存的,内存的读写速度⾮常快;
  2. redis是单线程的,省去了很多上下⽂切换线程的时间;
  3. redis使⽤多路复⽤技术,可以处理并发的连接。⾮阻塞IO 内部实现采⽤epoll,采⽤了epoll+⾃⼰实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利⽤epoll的多路复⽤特性,绝不在io上浪费⼀点时间。
Redis单线程的优劣势

单进程单线程优势

  1. 代码更清晰,处理逻辑更简单
  2. 不⽤去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁⽽导致的性能消耗
  3. 不存在多进程或者多线程导致的切换⽽消耗CPU

单进程单线程弊端
⽆法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善;

IO多路复⽤技术

redis 采⽤⽹络IO多路复⽤技术来保证在多连接的时候, 系统的⾼吞吐量。
多路-指的是多个socket连接,复⽤-指的是复⽤⼀个线程。多路复⽤主要有三种技术:select,poll,
epoll。epoll是最新的也是⽬前最好的多路复⽤技术。
这⾥“多路”指的是多个⽹络连接,“复⽤”指的是复⽤同⼀个线程。采⽤多路I/O

Redis⾼并发快总结
  1. Redis是纯内存数据库,⼀般都是简单的存取操作,线程占⽤的时间很多,时间的花费主要集中在IO上,所以读取速度快。
  2. 再说⼀下IO,Redis使⽤的是⾮阻塞IO,IO多路复⽤,使⽤了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下⽂的切换和竞争。
  3. Redis采⽤了单线程的模型,保证了每个操作的原⼦性,也减少了线程的上下⽂切换和竞争。
  4. 另外,数据结构也帮了不少忙,Redis全程使⽤hash结构,读取速度快,还有⼀些特殊的数据结构,对数据存储进⾏了优化,如压缩表,对短数据进⾏压缩存储,再如,跳表,使⽤有序的数据结构加快读取的速度。
  5. 还有⼀点,Redis采⽤⾃⼰实现的事件分离器,效率⽐较⾼,内部采⽤⾮阻塞的执⾏⽅式,吞吐能⼒⽐较⼤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一生酷到底

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值