Redis的87道高质量面试题

(1)请回答一下:复制,哨兵,集群的优缺点?

(1)复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
(2)哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷是写操作无法负载均衡;存储能力受到单机的限制。
在主从复制的基础上,哨兵引入了主节点的自动故障转移,进一步提高了Redis的高可用性;但是哨兵的缺陷同样很明显:哨兵无法对从节点进行自动故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要我们对从节点做额外的监控、切换操作。
此外,哨兵仍然没有解决写操作无法负载均衡、及存储能力受到单机限制的问题;这些问题的解决需要使用集群。
总结缺点:读从的时候不能故障转移,写主的时候不能负载均衡。
(3)集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

(2)Redis为什么会这么快?

1、Redis是纯内存操作,需要的时候可以以手动或自动化的方式将数据持久化到硬盘中
2、Redis是单线程,从而避开了多线程中上下文频繁切换而浪费CPU时间片的操作。
3、Redis中存储数据的数据类型大量使用高效率的数据结构算法,对数据的操作更加快捷高效
4、使用底层协议不同,比如Redis自创RESP通讯协议,即保证了运行效率又保证了数据通讯
5、使用多路I/O复用模型,非阻塞I/O

(3)哨兵有哪些功能?

(1)监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
(2)自动故障转移(Automatic Failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
(3)配置提供者(Configuration Provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
(4)通知(Notification):哨兵可以将故障转移的结果发送给客户端。其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现。

(4)介绍一下哨兵的工作原理?

(1)定时任务:每个哨兵节点维护了3个定时任务。定时任务的功能分别如下:通过向主从节点发送info命令获取最新的主从结构;通过发布订阅功能获取其他哨兵节点的信息;通过向其他节点发送ping命令进行心跳检测,判断是否下线。
(2)主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。
(3)客观下线:哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。需要特别注意的是,客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作。
(4)选举领导者哨兵节点:当主节点被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。
(5)故障转移:选举出的领导者哨兵,开始进行故障转移操作,该操作大体可以分为3个步骤:
(A)在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点;然后选择优先级最高的从节点(由slave-priority指定);如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择runid最小的从节点。
(B)更新主从状态:通过slaveof no one命令,让选出来的从节点成为主节点;并通过slaveof命令让其他节点成为其从节点。
(C)将已经下线的主节点(即6379)设置为新的主节点的从节点,当6379重新上线后,它会成为新的主节点的从节点。

(5)项目中哪里用到了Redis?

1、缓存数据:最常用,对经常需要查询且变动不是很频繁的数据存储进Redis,这些数据常称作热点数据。
2、消息队列:相当于消息订阅系统,比如ActiveMQ、RocketMQ。如果对数据有较高一致性要求时,还是建议使用专门的MQ框架)
3、计数器:比如统计点击率、点赞率,Redis具有原子性,可以避免并发问题
4、电商单页信息:商品单页面信息可以存储进Redis。
5、热点数据:比如新闻网站实时热点、微博热搜等,需要频繁更新。总数据量比较大的时候直接从数据库查询会影响性能。
6、地理位置:比如查询附近有哪些人,哪个商家等需求
7、可以结合自己的项目的使用情况进行解答

(6)项目中如何保障MySQL和Redis中的数据一致性的?

真正意义上或从项目运行效率上或从突然出现异常等原因来分析,数据库中的数据和缓存中的数据是不可能做到实时一致的,数据分为最终一致和强一致两类。如果业务中对数据的要求必须强一致,那么就不能使用缓存。缓存能做的只能保证数据的最终一致性。MySQL和Redis中的数据只能尽可能的保证数据的最终一致性。不管是先删库再删缓存,还是先删缓存再删库,都可能出现数据不一致的情况,只保障最终一致性即可。

(7)介绍一下内存淘汰?

内存淘汰是指用户存储的一部分key是可以根据不同的策略被Redis自动的删除,以释放内存资源,从而会出现从缓存中查不到数据的情况。
内存淘汰策略有2种算法
(1)LRU:最近最少使用Least Recently Used,删除很久没有访问的数据。
(2)LFU:最近最不常用Least Frequently Used,删除访问频率最少的数据。
Redis支持的内存淘汰方式有如下8种
(1)noeviction:不淘汰策略,存放的数据大于最大内存限制时会返回异常信息。
(2)volatile-lru:对具有过期的key使用lru。
(3)volatile-lfu:对具有过期的key使用lfu。
(4)volatile-random:对具有过期的key使用随机删除。
(5)volatile-ttl:对具有过期的key使用ttl最小值删除。
(6)allkeys-lru:对所有key采用lru。
(7)allkeys-lfu:对所有key采用lfu。
(8)allkeys-random:对所有key使用随机删除。

(8)介绍一下KEY的过期删除?

Redis存储数据时可以对KEY设置TTL过期时间,但并不只是单一的使用定时扫描删除策略,因为如果这样做,就需要一个定时器来不断的负责监控这些key,虽然内存释放了,但是非常消耗cpu资源。
Redis中对过期的key进行删除主要有两种方式
(1)定期删除:默认每100ms检测一次,遇到过期的key则进行删除,这里的检测并不是顺序检测,而是随机检测。那这样会不会有漏网之鱼没有被删除?肯定会有,这时可以采用惰性删除策略。
(2)惰性删除:当读或写一个已经过期的key时,会触发Redis的惰性删除策略,会删除过期的key。

(9)什么是缓存穿透?

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
(1)接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
(2)从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

(10)什么是缓存击穿?

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案
(1)设置热点数据永远不过期。
(2)加互斥锁,互斥锁参考代码如下:
在这里插入图片描述
1)缓存中有数据,直接走代码13行后就返回结果了
2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。
原理就是:缓存中没有数据时,排队操作数据库,降低数据库访问的压力,缺点是比较卡,可以采用限流的方式来进行优化,比如最多只能20个客户端一起访问。

(11)什么是缓存雪崩?

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
(1)缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
(2)如果缓存数据库是分布式部署,多区域查询。比如A集群KEY采用过期时间一样,而B集群中的KEY过期时间是随机的,当查询A集群没有数据时,到B集群进行查询。使用IF语句判断进行集群查询的切换。
(3)设置热点数据永远不过期。
(4)二级缓存,原始缓存失效时从复本缓存中读取数据。
(5)利用加锁或者队列方式避免过多请求同时对服务器进行读写操作。

(12)介绍一下Redis的优势?

1.)非常丰富的数据类型;
2.)Redis提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断;
3.)数据存在内存中,读写非常的高速,可以达到10w/s的频率。

(13)介绍一下主从复制?

(1)数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
(2)故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
(3)负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
(4)高可用基石:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
(5)全量复制:用于初次复制或其它无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
(6)部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。

(14)介绍一下RDB?

RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。RDB文件是经过压缩的二进制文件。
RDB是快照备份。

(15)介绍一下AOF?

RDB持久化是将进程数据写入文件,而AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中(有点像MySQL的binlog);当Redis重启时再次执行AOF文件中的命令来恢复数据。
AOF是增量备份。

(16)介绍一下AOF中的appendfsync同步数据的3种策略?

AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下:
(1)always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
每执行一个命令就开始备份。
(2)no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
根据操作系统来决定什么时候备份。
(3)everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是推荐的配置。
每隔一秒一备份。

(17)什么是AOF重写

随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。
文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件,不会对旧的AOF文件进行任何读取、写入操作。创建完新的AOF文件后会把旧的AOF文件覆盖掉。

(18)RDB和AOF的优缺点

RDB持久化
优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

AOF持久化
优点:支持秒级持久化、兼容性好。
缺点:文件大、恢复速度慢、对性能影响大。

使用bgsave命令实现全量备份持久化,而aof做增量持久化。bgsave会耗费较长时间,不够实时,如果有新的数据在没有备份的情况下有可能因为宕机,停电等原因会出现数据丢失,所以需要aof来配合使用。在Redis实例重启时,优先使用aof来恢复内存的状态,如果没有aof日志,就会使用rdb文件来恢复。

(19)如何用Redis实现乐观锁

在IT技术中,实现乐观锁大多数是基于数据版本(version)来进行实现的,也就是对数据增加一个版本标识。
在基于数据库表的版本version解决方案中,一般是通过为数据库表增加一个”version”列,来实现读取出数据时将此version版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。
Redis中可以使用watch命令来监视给定的key,当执行exec的时候,如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。
注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。

(20)介绍一下在Redis中实现乐观锁的步骤

在Redis中实现乐观锁的原理:使用watch监视一个key的值是否发生改变。
比如有两个客户端A和B,还有key为mykey,value为oldvalue。
(1)A客户端使用watch对mykey的值oldvalue进行监视
(2)A客户端执行MULTI
(3)A客户端执行set x xx
(4)A客户端执行set y yy
(5)B客户端set mykey newvalue
(6)A客户端执行EXEC时发现mykey的值已经由oldvalue改成newvalue,说明mykey的值被B客户端所更改。
(7)A客户端回滚事务,不再提交

(21)介绍一下Redis中的事务?

Redis中的事务(transaction)是一组命令的集合。
事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。Redis的事务是下面4个命令来实现
(1)multi:开启Redis的事务。
(2)exec:提交事务,执行从multi到此命令前的命令队列。
(3)discard:取消事务。
(4)watch:监视key对应的值。如果事务使用exec提交时,发现监视key对应的值发生了变化,事务将被取消。

(22)Redis的数据类型及各自使用场景

看到这个问题,是不是觉得它很基础?其实笔者也这么觉得。然而根据面试经验发现,至少80%的人答不上这个问题。建议在项目中用到后,再类比记忆,体会更深,不要硬记。基本上,一个合格的程序员常见五种数据类型都会用到:
1、String
这个其实没什么好说的,最常规的Set/Get操作,Value可以是String也可以是数字,一般做一些复杂的计数功能的缓存。
2、Hash
这里Value存放的是结构化的对象,比较方便的就是操作其中的某个字段。笔者在做单点登录的时候,就是用这种数据结构存储用户信息,以CookieId作为Key,设置30分钟为缓存过期时间,能很好地模拟出类似Session的效果。
3、List
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用Lrange命令,做基于Redis的分页功能,性能极佳,用户体验好。
4、Set
因为Set堆放的是一堆不重复值的集合,所以可以做全局去重的功能。
为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set比较麻烦,难道为了做一个全局去重,再起一个公共服务?太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好、全部的喜好、自己独有的喜好等功能。
5、Sorted Set
Sorted Set多了一个权重参数Score,集合中的元素能够按Score进行排列。可以做排行榜应用,取TOP N操作。另外,Sorted Set还可以用来做延时任务。还可以实现按范围查找。

(23)Redis有哪些常见数据类型?

String、Hash、List、Set、SortedSet、HyperLogLog、Geo、Pub/Sub,Streams。

(24)介绍一下什么是分布式锁以及如何在Redis中实现分布式锁?

传统的锁Lock大多数代表只是在当前进程中的不同线程之间进行互斥,不能实现进程与进程之间的互斥,想要实现就要使用分布式锁。
在分布式锁的环境中,每个进程争抢外部的分布式锁,哪个进程抢到了锁, 这个进程就拥有锁,其它进程等待持有锁的进程释放锁,然后再继续后面的争抢锁,以此循环。
使用Redisson框架实现分布式锁示例代码如下:

public class Test1 {
	public static void main(String[] args) throws InterruptedException {
		String username = "我是" + Math.random() + "进程";
		Config config = new Config();
		SingleServerConfig singleServerConfig = config.useSingleServer();
		singleServerConfig.setAddress("redis://192.168.1.103:7777").setPassword("accp");
		RedissonClient redisson = Redisson.create(config);

		RLock lock = redisson.getLock("lock");
		lock.lock();

		for (int i = 0; i < 30; i++) {
			System.out.println(username + " i=" + (i + 1));
			Thread.sleep(1000);
		}

		lock.unlock();
		redisson.shutdown();
	}
}

(25)keys与scan命令使用的场景

问:假如Redis里面有10亿个key,其中有100w个key是以某个固定前缀开头的,如何将它们全部找出来?
答:使用keys指令可以扫出指定模式的key列表。
问:如果这个Redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
答:由于Redis是单线程的,所以当执行keys命令后会导致线上服务停顿,直到指令执行完毕,服务才能恢复。
问:如何解决这个问题?
答:这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
总结:在线系统一定不要使用keys命令。

(26)在Redis中如何实现队列?

答:一般使用list数据类型作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
问:可不可以不用sleep呢?
答:list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
问:能不能生产一次消费多次呢?
答:使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
问:pub/sub有什么缺点?
答:在消费者下线的情况下,生产的消息会丢失,可以使用Streams类型来解决,或者使用专业的消息队列框架,如rabbitmq等。
问:在Redis中如何实现延时队列?
答:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

(27)如果有大量的key需要设置同一时间过期,一般需要注意什么?

如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

(28)介绍一下混合持久化?

Redis4.0之后有了混合持久化的功能,将bgsave的全量和aof的增量做了融合处理,两种备份方式备份的数据保存在同一个AOF文件中,这样既保证了恢复的效率又兼顾了数据的安全性。

(29)在Redis集群中如何实现数据分片分区?

在Redis中使用虚拟槽对数据进行分片分区。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(30)Pipeline有什么好处,为什么要用pipeline?

Pipeline的作用可以将多次IO往返的时间缩减为一次,也就是在这一次访问Redis服务器时发送多个命令,让Redis一起执行,减少多次request-response的时间用时。使用pipeline技术的前提是pipeline执行的指令之间没有因果相关性。

(31)Redis Cluster集群架构的优势有哪些呢?

(1)支持水平扩容 N个Redis master node,并且每个master node同样可以挂载 N 个 slave node
(2)读写分离的架构
(3)高并发:可同时对Redis cluster中的不同节点进行访问。
(4)高可用(无需sentinel 哨兵监控,如果master 挂了,Redis cluster内部自动将slave切换master)

(32)Redis中的集群cluster和主从复制replication+哨兵sentinal的使用场景是什么?

(1)两者都是解决Redis 单机瓶颈问题(宕机不可用,低QPS,性能差等)
(2)根据自身数据量的需求选择合适解决方案
(A)数据量很少(几个G),Redis 单机就可以解决
(B)数据量大, 一主多从(1 master N slave ,具体根据自身读吞吐量而定) + 哨兵集群(sentinal 保证高可用)
(C)海量数据,Redis cluster 集群(N master N slave , 海量数据 + 高并发+ 高可用)

(33)介绍一下数据分片的算法?

(1)首先讲下最原始的Hash算法,如下图所示:
(2)一致性 Hash 算法(自动缓存迁移) + 虚拟节点(自动负载均衡)

一致性hash算法在一定程度上解决了node宕机后的大部分数据失效问题,但是也会导致node 的热点问题,降低性能,这个又该怎么解决呢? 可以通过增加虚拟节点的方式 让hash点散落更均匀,不光能解决热点问题,还可以达到自动的负载均衡效果,如下图所示。
在这里插入图片描述
在这里插入图片描述

一致性hash算法在一定程度上解决了node宕机后的大部分数据失效问题,但是也会导致node 的热点问题,降低性能,这个又该怎么解决呢? 可以通过增加虚拟节点的方式 让hash点散落更均匀,不光能解决热点问题,还可以达到自动的负载均衡效果,如下图所示。
(3)Redis cluster采用的是hash slot算法
Redis cluster拥有固定的16384个slot (槽) ;这个槽是虚拟的,并不是真正存在。slot 被分布到各个master中,当某个key映射到某个master负责的槽时,就由对应的master为key提供服务。在Redis cluster中,只有master才拥有对slot的所有权,slave只负责使用slot,并没有所有权。
键key分布基本算法的映射公式如下:
HASH_SLOT=CRC16(key) mod 16384
Redis cluster通过对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot。Redis cluster中每个master都会持有部分slot,比如有3个master,那么可能每个master持有5000多个hash slot。hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去,移动 hash slot 的成本是非常低的。由于 16384 是固定的,当某个master宕机时,不会影响其他机器的数据,因为key找得是hash slot ,而不是机器。

(34)介绍一下Redis slowlog?

Redis是目前最流行的缓存系统,因其丰富的数据类型和良好的性能表现,被各大公司广泛使用。尽管Redis性能极佳,但若不注意使用方法,极容易出现慢查询,慢查询多了或者一个20s的慢查询会导致操作队列(Redis是单进程)堵塞,最终引起雪崩甚至整个服务不可用。对于慢查询语句,Redis提供了相关的配置和命令。
配置有两个:
(1)slowlog-log-slower-than:当命令执行时间(不包括排队时间)超过该时间时会被记录下来,单位为 微秒,比如通过下面的命令,就可以记录执行时长超过 20ms的命令了。
config set slowlog-log-slower-than 20000
(2)slowlog-max-len:可以记录的慢查询命令的总数,比如通过下面的命令,就可以记录最近100条慢查询命令了。
config set slowlog-max-len 100

命令有两个:
(1)slowlog get [len]:获取指定长度的慢查询列表。
Redis 127.0.0.1:6379> slowlog get 2

    1. (integer) 14
    2. (integer) 1309448221
    3. (integer) 15
      1. “ping”
    1. (integer) 13
    2. (integer) 1309448128
    3. (integer) 30
      1. “slowlog”
      2. “get”
      3. “100”

上面返回了两个慢查询命令,其中每行的含义如下:
(A)第一行是一个慢查询命令的id。该id是自增的,只有在 Redis server 重启时该id才会重置。
(B)第二行是慢查询命令执行的时间戳
(C)第三行是慢查询命令执行耗时,单位为微秒
(D)第四行是慢查询命令的具体内容。
(2)slowlog reset:清空慢查询日志队列。

(35)Redis架构之防雪崩设计:网站不宕机背后的兵法

一、缓存穿透预防及优化
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层,如下图:
缓存穿透模型
上图整个过程分为如下3步:
(1)缓存层不命中
(2)存储层不命中,所以不将null空结果写回缓存
(3)返回空结果给客户端
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。
缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。解决办法通常可以在程序中采用log记录的方式分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题。
造成缓存穿透的基本有两个。第一,业务自身代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空命中,下面来看一下如何解决缓存穿透问题。
缓存穿透的解决方法
1)缓存null空对象
如下图所示:
缓存空值应对穿透问题

当第 2 步存储层不命中后,仍然将null空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
缓存空对象会有两个问题:
第一,null空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动删除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5 分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
下面给出了缓存空对象的实现伪代码:
在这里插入图片描述

2)布隆过滤器拦截
如下图所示:
使用布隆过滤器应对穿透问题

在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。例如:一个个性化推荐系统有4亿个用户ID,每个小时算法工程师会根据每个用户之前历史行为做出来的个性化放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有有个性化推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户ID不存在,那么就不会访问存储层,在一定程度保护了存储层。
有关布隆过滤器的相关知识,可以参考网址:
https://en.wikipedia.org/wiki/Bloom_filter
这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。
前面介绍了缓存穿透问题的两种解决方法 ( 实际上这个问题是一个开放问题,有很多解决方法 ),下面通过下表从适用场景和维护成本两个方面对两种方案“缓存空对象和布隆过滤器方案”对比进行分析。
在这里插入图片描述

二、缓存雪崩问题优化
从下图可以很清晰分析出什么是缓存雪崩:由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。 缓存雪崩的英文原意是 stampeding herd(奔逃的野牛),指的是缓存层宕掉后,流量会像奔逃的野牛一样,打向后端存储。
缓存层不可用引起的雪崩
预防和解决缓存雪崩问题,可以从以下三个方面进行着手。
1)保证缓存层服务高可用性。
和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。
2)依赖隔离组件为后端限流并降级。
无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang 在这个资源上,造成整个系统不可用。降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。在实际项目中,我们需要对重要的资源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池,开启资源池,资源池阀值管理,这些做起来还是相当复杂的,这里推荐一个 Java 依赖隔离工具 Hystrix(https://github.com/Netflix/Hystrix),如下图所示。
Hystrix 是解决依赖隔离的利器,但是该内容已经超出本书的范围,同时只适用于 Java 应用,所以这里不会详细介绍。
在这里插入图片描述
3)提前演练。在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。

三、缓存热点key重建优化
开发人员使用缓存+过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大,重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。在缓存失效的瞬间,有大量线程来重建缓存 ( 如下图),造成后端负载加大,甚至可能会让应用崩溃。
热点 key 失效后大量线程重建缓存
要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:
(A)减少重建缓存的次数
(B)数据尽可能一致
(C)较少的潜在危险

1)互斥锁 (mutex key)
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可,整个过程如下图所示。
使用互斥锁重建缓存

下面代码使用 Redis 的 setnx 命令实现上述功能。
在这里插入图片描述
(1) 从Redis获取数据,如果值不为空,则直接返回值,否则执行 (2.1) 和 (2.2)。
(2.1) 如果set(nx和ex) 结果为true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。
(2.2) 如果setnx(nx和ex) 结果为false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间 ( 例如这里是50毫秒,取决于构建缓存的速度 ) 后,重新执行函数,直到获取到数据。

2)永远不过期
实现永远不过期可以使用2种方式:
(A)确实没有对key设置TTL过期时间,所以不会出现热点key过期后产生自动删除的问题,也就是“物理”不过期。
优点:只执行1次set,后面任意次get。
缺点:值不会改变,有可能永远是旧值。
(B)为每个 value 设置一个逻辑过期时间(比如key对应的value中存储一个json字符串,json字符串包含data存储数据的属性,以及ttl数据过期时间的属性),当访问这个key时,发现当前时间超过json中的ttl时间,会使用单独的线程去数据库取回最新的数据,然后对Redis中的旧值进行覆盖。
优点:阶段性的存储新值
缺点:自己写实现代码

从实战看,此方法有效杜绝了热点key产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。下面代码使用Redis进行模拟:
在这里插入图片描述
作为一个并发量较大的应用,在使用缓存时有三个目标:
第一, 加快用户访问速度,提高用户体验。
第二, 降低后端负载,减少潜在的风险,保证系统平稳。
第三, 保证数据“尽可能”及时更新。下面将按照这三个维度对上述两种解决方案进行分析。

(1)互斥锁 (mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
(2)“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
两种解决方法对比如下表所示。
在这里插入图片描述

(36)配置文件redis.conf配置介绍

在配置文件的开头部分,首先明确了一些度量单位:

1k => 1000 bytes

1kb => 1024 bytes

1m => 1000000 bytes

1mb => 1024*1024 bytes

1g => 1000000000 bytes

1gb => 102410241024 bytes

可以看出,redis配置中对单位的大小写不敏感,1GB、1Gb和1gB都是相同的。由此也说明,redis只支持bytes,不支持bit单位。

redis支持“主配置文件中引入外部配置文件”,很像C/C++中的include指令,比如:
include /path/to/other.conf

如果你看过redis的配置文件,会发现还是很有条理的。redis配置文件被分成了几大块区域,它们分别是:
1.通用(general)
2.快照(snapshotting)
3.复制(replication)
4.安全(security)
5.限制(limits)
6.追加模式(append only mode)
7.LUA脚本(lua scripting)
8.慢日志(slow log)

下面我们就来逐一讲解。
默认情况下,redis并不是以daemon形式来运行的。通过daemonize配置项可以控制redis的运行形式,如果改为yes,那么redis就会以daemon形式运行:
daemonize no

当以daemon形式运行时,redis会生成一个pid文件,默认会生成在/var/run/redis.pid。当然,你可以通过pidfile来指定pid文件生成的位置,比如:
pidfile /path/to/redis.pid

默认情况下,redis会响应本机所有可用网卡的连接请求。当然,redis允许你通过bind配置项来指定要绑定的IP,比如:
bind 192.168.1.2 10.8.4.2

redis的默认服务端口是6379,你可以通过port配置项来修改。如果端口设置为0的话,redis便不会监听端口了。
port 6379

有些同学会问“如果redis不监听端口,还怎么与外界通信呢”,其实redis还支持通过unix socket方式来接收请求。可以通过unixsocket配置项来指定unix socket文件的路径,并通过unixsocketperm来指定文件的权限。
unixsocket /tmp/redis.sock
unixsocketperm 755

当一个redis-client一直没有请求发向server端,那么server端有权主动关闭这个连接,可以通过timeout来设置“空闲超时时限”,0表示永不关闭。
timeout 0

TCP连接保活策略,可以通过tcp-keepalive配置项来进行设置,单位为秒,假如设置为60秒,则server端会每60秒向连接空闲的客户端发起一次ACK请求,以检查客户端是否已经挂掉,对于无响应的客户端则会关闭其连接。所以关闭一个连接最长需要120秒的时间。如果设置为0,则不会进行保活检测。
tcp-keepalive 0

redis支持通过loglevel配置项设置日志等级,共分四级,即debug、verbose、notice、warning。
loglevel notice

redis也支持通过logfile配置项来设置日志文件的生成位置。如果设置为空字符串,则redis会将日志输出到标准输出。假如你在daemon情况下将日志设置为输出到标准输出,则日志会被写到/dev/null中。
logfile “”

如果希望日志打印到syslog中,也很容易,通过syslog-enabled来控制。另外,syslog-ident还可以让你指定syslog里的日志标志,比如:
syslog-ident redis

而且还支持指定syslog设备,值可以是USER或LOCAL0-LOCAL7。具体可以参考syslog服务本身的用法。
syslog-facility local0

对于redis来说,可以设置其数据库的总数量,假如你希望一个redis包含16个数据库,那么设置如下:
databases 16
这16个数据库的编号将是0到15。默认的数据库是编号为0的数据库。用户可以使用select 来选择相应的数据库。

快照,主要涉及的是redis的RDB持久化相关的配置,我们来一起看一看。
我们可以用如下的指令来让数据保存到磁盘上,即控制RDB快照功能:
save
举例来说:
save 900 1 //表示每15分钟且至少有1个key改变,就触发一次持久化
save 300 10 //表示每5分钟且至少有10个key改变,就触发一次持久化
save 60 10000 //表示每60秒至少有10000个key改变,就触发一次持久化

如果你想禁用RDB持久化的策略,只要不设置任何save指令就可以,或者给save传入一个空字符串参数也可以达到相同效果,就像这样:
save “”

如果用户开启了RDB快照功能,那么在redis持久化数据到磁盘时如果出现失败,默认情况下,redis会停止接受所有的写请求。这样做的好处在于可以让用户很明确的知道内存中的数据和磁盘上的数据已经存在不一致了。如果redis不顾这种不一致,一意孤行的继续接收写请求,就可能会引起一些灾难性的后果。
如果下一次RDB持久化成功,redis会自动恢复接受写请求。
当然,如果你不在乎这种数据不一致或者有其他的手段发现和控制这种不一致的话,你完全可以关闭这个功能,以便在快照写入失败时,也能确保redis继续接受新的写请求。配置项如下:
stop-writes-on-bgsave-error yes

对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。
rdbcompression yes

在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果你希望获取到最大的性能提升,可以关闭此功能。
rdbchecksum yes

我们还可以设置快照文件的名称,默认是这样配置的:
dbfilename dump.rdb

最后,你还可以设置这个快照文件存放的路径。比如默认设置就是当前文件夹:
dir ./

redis提供了主从同步功能。
通过slaveof配置项可以控制某一个redis作为另一个redis的从服务器,通过指定IP和端口来定位到主redis的位置。一般情况下,我们会建议用户为从redis设置一个不同频率的快照持久化的周期,或者为从redis配置一个不同的服务端口等等。
slaveof

如果主redis设置了验证密码的话(使用requirepass来设置),则在从redis的配置中要使用masterauth来设置校验密码,否则的话,主redis会拒绝从redis的访问请求。

masterauth

当从redis失去了与主redis的连接,或者主从同步正在进行中时,redis该如何处理外部发来的访问请求呢?这里,从redis可以有两种选择:

第一种选择:如果slave-serve-stale-data设置为yes(默认),则从redis仍会继续响应客户端的读写请求。
第二种选择:如果slave-serve-stale-data设置为no,则从redis会对客户端的请求返回“SYNC with master in progress”,当然也有例外,当客户端发来INFO请求和SLAVEOF请求,从redis还是会进行处理。

你可以控制一个从redis是否可以接受写请求。将数据直接写入从redis,一般只适用于那些生命周期非常短的数据,因为在主从同步时,这些临时数据就会被清理掉。自从redis2.6版本之后,默认从redis为只读。
slave-read-only yes

只读的从redis并不适合直接暴露给不可信的客户端。为了尽量降低风险,可以使用rename-command指令来将一些可能有破坏力的命令重命名,避免外部直接调用。比如:
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52

从redis会周期性的向主redis发出PING包。你可以通过repl_ping_slave_period指令来控制其周期。默认是10秒。
repl-ping-slave-period 10

在主从同步时,可能在这些情况下会有超时发生:
以从redis的角度来看,当有大规模IO传输时。
以从redis的角度来看,当数据传输或PING时,主redis超时
以主redis的角度来看,在回复从redis的PING时,从redis超时

用户可以设置上述超时的时限,不过要确保这个时限比repl-ping-slave-period的值要大,否则每次主redis都会认为从redis超时。
repl-timeout 60

我们可以控制在主从同步时是否禁用TCP_NODELAY。如果开启TCP_NODELAY,那么主redis会使用更少的TCP包和更少的带宽来向从redis传输数据。但是这可能会增加一些同步的延迟,大概会达到40毫秒左右。如果你关闭了TCP_NODELAY,那么数据同步的延迟时间会降低,但是会消耗更多的带宽。(如果你不了解TCP_NODELAY,可以到这里来科普一下)。
repl-disable-tcp-nodelay no

我们还可以设置同步队列长度。队列长度(backlog)是主redis中的一个缓冲区,在与从redis断开连接期间,主redis会用这个缓冲区来缓存应该发给从redis的数据。这样的话,当从redis重新连接上之后,就不必重新全量同步数据,只需要同步这部分增量数据即可。
repl-backlog-size 1mb

如果主redis等了一段时间之后,还是无法连接到从redis,那么缓冲队列中的数据将被清理掉。我们可以设置主redis要等待的时间长度。如果设置为0,则表示永远不清理。默认是1个小时。
repl-backlog-ttl 3600

我们可以给众多的从redis设置优先级,在主redis持续工作不正常的情况,优先级高的从redis将会升级为主redis。而编号越小,优先级越高。比如一个主redis有三个从redis,优先级编号分别为10、100、25,那么编号为10的从redis将会被首先选中升级为主redis。当优先级被设置为0时,这个从redis将永远也不会被选中。默认的优先级为100。
slave-priority 100

假如主redis发现有超过M个从redis的连接延时大于N秒,那么主redis就停止接受外来的写请求。这是因为从redis一般会每秒钟都向主redis发出PING,而主redis会记录每一个从redis最近一次发来PING的时间点,所以主redis能够了解每一个从redis的运行情况。
min-slaves-to-write 3
min-slaves-max-lag 10

上面这个例子表示,假如有大于等于3个从redis的连接延迟大于10秒,那么主redis就不再接受外部的写请求。上述两个配置中有一个被置为0,则这个特性将被关闭。默认情况下min-slaves-to-write为0,而min-slaves-max-lag为10。

我们可以要求redis客户端在向redis-server发送请求之前,先进行密码验证。当你的redis-server处于一个不太可信的网络环境中时,相信你会用上这个功能。由于redis性能非常高,所以每秒钟可以完成多达15万次的密码尝试,所以你最好设置一个足够复杂的密码,否则很容易被黑客破解。
requirepass importsource
这里我们通过requirepass将密码设置成“芝麻开门”。

redis允许我们对redis指令进行更名,比如将一些比较危险的命令改个名字,避免被误执行。比如可以把CONFIG命令改成一个很复杂的名字,这样可以避免外部的调用,同时还可以满足内部调用的需要:
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c89

我们甚至可以禁用掉CONFIG命令,那就是把CONFIG的名字改成一个空字符串:
rename-command CONFIG “”

但需要注意的是,如果你使用AOF方式进行数据持久化,或者需要与从redis进行通信,那么更改指令的名字可能会引起一些问题。

我们可以设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自身内部处理逻辑留一些句柄出来。
如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
maxclients 10000

我们甚至可以设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
如果redis无法根据移除规则来移除内存中的数据,或者我们设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。但是对于无内存申请的指令,仍然会正常响应,比如GET等。
maxmemory

需要注意的一点是,如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
对于内存移除规则来说,redis提供了多达6种的移除规则。他们是:
1.volatile-lru:使用LRU算法移除过期集合中的key
2.allkeys-lru:使用LRU算法移除key
3.volatile-random:在过期集合中移除随机的key
4.allkeys-random:移除随机的key
5.volatile-ttl:移除那些TTL值最小的key,即那些最近才过期的key。
6.noeviction:不进行移除。针对写操作,只是返回错误信息。
无论使用上述哪一种移除规则,如果没有合适的key可以移除的话,redis都会针对写请求返回错误信息。
maxmemory-policy volatile-lru
LRU算法和最小TTL算法都并非是精确的算法,而是估算值。所以你可以设置样本的大小。假如redis默认会检查三个key并选择其中LRU的那个,那么你可以改变这个key样本的数量。
maxmemory-samples 3
最后,我们补充一个信息,那就是到目前版本(2.8.4)为止,redis支持的写指令包括了如下这些:
set setnx setex append
incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
getset mset msetnx exec sort

默认情况下,redis会异步的将数据持久化到磁盘。这种模式在大部分应用程序中已被验证是很有效的,但是在一些问题发生时,比如断电,则这种机制可能会导致数分钟的写请求丢失。
如本文上半部分中介绍的,追加文件(Append Only File)是一种更好的保持数据一致性的方式。即使当服务器断电时,也仅会有1秒钟的写请求丢失,当redis进程出现问题且操作系统运行正常时,甚至只会丢失一条写请求。
我们建议大家,AOF机制和RDB机制可以同时使用,不会有任何冲突。对于如何保持数据一致性的讨论,请参见Redis持久化:RDB与AOF。
appendonly no

我们还可以设置aof文件的名称:
appendfilename “appendonly.aof”

fsync()调用,用来告诉操作系统立即将缓存的指令写入磁盘。一些操作系统会“立即”进行,而另外一些操作系统则会“尽快”进行。
redis支持三种不同的模式:
1.no:不调用fsync()。而是让操作系统自行决定sync的时间。这种模式下,redis的性能会最快。
2.always:在每次写请求后都调用fsync()。这种模式下,redis会相对较慢,但数据最安全。
3.everysec:每秒钟调用一次fsync()。这是性能和安全的折衷。
默认情况下为everysec。有关数据一致性的揭秘,可以参考Redis持久化:RDB与AOF。

appendfsync everysec
当fsync方式设置为always或everysec时,如果后台持久化进程需要执行一个很大的磁盘IO操作,那么redis可能会在fsync()调用时卡住。目前尚未修复这个问题,这是因为即使我们在另一个新的线程中去执行fsync(),也会阻塞住同步写调用。

为了缓解这个问题,我们可以使用下面的配置项,这样的话,当BGSAVE或BGWRITEAOF运行时,fsync()在主进程中的调用会被阻止。这意味着当另一路进程正在对AOF文件进行重构时,redis的持久化功能就失效了,就好像我们设置了“appendsync none”一样。如果你的redis有时延问题,那么请将下面的选项设置为yes。否则请保持no,因为这是保证数据完整性的最安全的选择。
no-appendfsync-on-rewrite no
我们允许redis自动重写aof。当aof增长到一定规模时,redis会隐式调用BGREWRITEAOF来重写log文件,以缩减文件体积。
redis是这样工作的:redis会记录上次重写时的aof大小。假如redis自启动至今还没有进行过重写,那么启动时aof文件的大小会被作为基准值。这个基准值会和当前的aof大小进行比较。如果当前aof大小超出所设置的增长比例,则会触发重写。另外,你还需要设置一个最小大小,是为了防止在aof很小时就触发重写。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
如果设置auto-aof-rewrite-percentage为0,则会关闭此重写功能。

redis慢日志是指一个系统进行日志查询超过了指定的时长。这个时长不包括IO操作,比如与客户端的交互、发送响应内容等,而仅包括实际执行查询命令的时间。
针对慢日志,你可以设置两个参数,一个是执行时长,单位是微秒,另一个是慢日志的长度。当一个新的命令被写入日志时,最老的一条会从命令日志队列中被移除。
单位是微秒,即1000000表示一秒。负数则会禁用慢日志功能,而0则表示强制记录每一个命令。
slowlog-log-slower-than 10000
慢日志最大长度,可以随便填写数值,没有上限,但要注意它会消耗内存。你可以使用SLOWLOG RESET来重设这个值。
slowlog-max-len 128

有关哈希数据结构的一些配置项:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

有关列表数据结构的一些配置项:
list-max-ziplist-entries 512
list-max-ziplist-value 64

有关集合数据结构的配置项:
set-max-intset-entries 512

有关有序集合数据结构的配置项:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

关于是否需要再哈希的配置项:
activerehashing yes

关于客户端输出缓冲的控制项:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

有关频率的配置项:
hz 10

有关重写aof的配置项
aof-rewrite-incremental-fsync yes

(37)参考资料

https://mp.weixin.qq.com/s?__biz=MzU0NDEyODkzMQ==&mid=2247486862&idx=1&sn=1b1e56a83cc5d3c5a7894fc0dec6fc1a&source=41#wechat_redirect

https://mp.weixin.qq.com/s?__biz=MzAxNzk2MzgyNg==&mid=2247483651&idx=1&sn=5427b064ed06d857772d1b645512b135&scene=1&srcid=0523wcTjiynuvAXodgjjk9BZ#rd

https://www.cnblogs.com/telwanggs/p/10642382.html

https://www.cnblogs.com/duguxiaobiao/p/11720603.html

(38)Redis中常见数据类型最大存储量

(1)Strings类型:一个String类型的value最大可以存储512M
(2)Lists类型:list的元素个数最多为2^32-1个,也就是4294967295个。
(3)Sets类型:元素个数最多为2^32-1个,也就是4294967295个。
(4)Hashes类型:键值对个数最多为2^32-1个,也就是4294967295个。
(5)Sorted sets类型:跟Sets类型相似

(39)Redis和Redisson有什么关系?

Redisson是一个高级的分布式协调Redis客户端框架,能帮助用户在分布式环境中轻松实现Java中的对象功能,比如 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

(40)Jedis与Redisson对比有什么优缺点?

Jedis:
优点:对Redis命令提供了比较全面的支持,使用简单方便。
缺点:在分布式解决方案方面支持较少。

Redisson:
优点:提供大量针对分布式环境下的Java类,实现了分布式和可扩展的Java数据结构,提供更多分布式数据处理相关的解决方案,比如分布式锁等。
缺点:和Jedis相比,支持Redis命令比较少,比如不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。

(41)什么是Redis?

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

(42)Redis相比memcached有哪些优势?

(1) memcached所有的值均是简单的字符串,redis作为其替代者, 支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据

(43)Redis支持哪几种数据类型?

String、List、Set、Sorted Set、hashes

(44)Redis主要消耗什么物理资源?

redis是一种基于内存高性能的数据库— 主要依赖于内存内存。

(45)Redis的全称是什么?

Remote Dictionary Server

(46)Redis官方为什么不提供Windows版本?

因为目前Linux版本已经相当稳定,而且用户量很大,无需开发windows版本,反而会带来兼容性等问题。

(47)一个字符串类型的值能存储最大容量是多少?

512M

(48)为什么Redis需要把所有数据放到内存中?

Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。
如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

(49)Redis集群方案应该怎么做?都有哪些方案?

1.twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
2.codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。
3.redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
4.在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。 这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

(50)Redis集群方案什么情况下会导致整个集群不可用?

有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。

(51)MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?

redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

(52)Redis有哪些适合的场景?

(1)会话缓存(Session Cache)最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。
(2)、全页缓存(FPC)除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
(3)、队列Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。
(4),排行榜/计数器Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
(5)、发布/订阅最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。

(53)Redis支持的Java客户端都有哪些?官方推荐用哪个?

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。

(54)Redis和Redisson有什么关系?

Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

(55)Jedis与Redisson对比有什么优缺点?

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

(56)Redis如何设置密码及验证密码?

设置密码:config set requirepass 123456授权密码:auth 123456

(57)说说Redis哈希槽的概念?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

(58)Redis集群的主从复制模型是怎样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

(59)Redis集群会有写操作丢失吗?为什么?

Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

(60)Redis集群之间是如何复制的?

异步复制

(61)Redis集群最大节点个数是多少?

16384个。

(62)Redis集群如何选择数据库?

Redis集群目前无法做数据库选择,默认在0数据库。

(63)怎么测试Redis的连通性?

ping

(64)Redis中的管道有什么用?

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

(65)怎么理解Redis事务?

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

(66)Redis事务相关的命令有哪几个?

MULTI、EXEC、DISCARD、WATCH ##28、Redis key的过期时间和永久有效分别怎么设置? EXPIRE和PERSIST命令。

(67)Redis如何做内存优化?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.

(68)Redis回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

(69)Redis回收使用的是什么算法?

LRU算法

(70)Redis如何做大量数据插入?

Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。

(71)为什么要做Redis分区?

分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。

(72)你知道有哪些Redis分区实现方案?

客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

(73)Redis分区有什么缺点?

涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。同时操作多个key,则不能使用Redis事务.分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。

(74)Redis持久化数据和缓存怎么做扩容?

如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

(75)分布式Redis是前期做还是后期规模上来了再做好?为什么?

既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。

(76)支持一致性哈希的客户端有哪些?

Redis-rb、Predis等。

(77)Redis与其他key-value存储有什么不同?

Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

(78)Redis的内存占用情况怎么样?

给你举个例子: 100万个键值对(键是0到999999值是字符串“hello world”)在我的32位的Mac笔记本上 用了100MB。同样的数据放到一个key里只需要16MB, 这是因为键值有一个很大的开销。 在Memcached上执行也是类似的结果,但是相对Redis的开销要小一点点,因为Redis会记录类型信息引用计数等等。当然,大键值对时两者的比例要好很多。64位的系统比32位的需要更多的内存开销,尤其是键值对都较小时,这是因为64位的系统里指针占用了8个字节。 但是,当然,64位系统支持更大的内存,所以为了运行大型的Redis服务器或多或少的需要使用64位的系统。

(79)都有哪些办法可以降低Redis的内存使用情况呢?

如果你使用的是32位的Redis实例,可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。

(80)查看Redis使用情况及状态信息用什么命令?

info

(81)Redis的内存用完了会发生什么?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将Redis当缓存来使用配置淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。

(82)Redis是单线程的,如何提高多核CPU的利用率?

可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。

(83)一个Redis实例最多能存放多少的keys?

List、Set、Sorted Set他们最多能存放多少元素?理论上Redis可以处理多达232的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。任何list、set、和sorted set都可以放232个元素。换句话说,Redis的存储极限是系统中的可用内存值。

(84)Redis常见性能问题和解决方案?

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

(85)Redis提供了哪几种持久化方式?

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。

(86)如何选择合适的持久化方式?

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

(87)修改配置不重启Redis会实时生效吗?

针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。

发布了43 篇原创文章 · 获赞 72 · 访问量 6375

猜你喜欢

转载自blog.csdn.net/qq_43107323/article/details/104532857
今日推荐