1.什么是Redis?
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API的非关系型数据库。
redis是一个以key-value存储的数据库结构型服务器,它支持的数据结构类型包括:字符串(String)、链表(lists)、哈希表(hash)、集合(set)、有序集合(Zset)等。为了保证读取的效率,redis把数据对象都存储在内存当中,它可以支持周期性的把更新的数据写入磁盘文件中。而且它还提供了交集和并集,以及一些不同方式排序的操作。
2.Redis的特点
1.redis数据读写速度非常快,因为它把数据都读取到内存当中操作,而且redis是用C语言编写的,是最“接近”操作系统的语言,所以执行速度相对较快。
2.redis虽然数据的读取都存在内存当中,但是最终它是支持数据持久化到磁盘当中。
3.redis提供了丰富的数据结构。
4.redis的所有操作都是原子性,支持事务,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。
5.redis支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
3.Redis是单进程单线程的为什么还能这么快?
1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
2.数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的。
3.采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
4.使用多路I/O复用模型,非阻塞IO。
4.什么是多路I/O复用
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
5.Redis虚拟内存
和大多数NoSQL一样,Redis也是使用的KV的数据存储模型,为了提高读写效率,Redis也是将KV保存在内存中,但是这样做会带来一些问题。
如果内存足够大,那么把所有的数据都加载到内存中固然是最好的选择,那么查询操作都直接操作内存,自然是很快。但是实际情况中却更多的是只有部分数据才能加载到内存中,那么这个时候就提出来一个虚拟内存的概念。
Redis中的虚拟内存和Linux中的虚拟内存不是一回事,但是其思想是一致的,就是把暂时不常用的数据从内存交换到磁盘中,从而可以把宝贵的内存腾出来用于其他需要访问的数据,尤其是像Redis这样的内存数据库,内存的数量无意会成为一个瓶颈。虽然我们可以把数据分割到多个服务器上,但是虚拟内存依然是一个相当有效的解决办法。
使用虚拟内存还有另外一个原因,那就是在我们的数据库中,被访问的数据总是占全部数据的一小部分,而大部分的数据都是很少被访问的,就网站用户来说,活跃用户总是占据少部分。
6.Redis分布式锁
分布式锁的实现方式有三种,分别为数据库乐观锁、Redis的分布式锁和ZooKeeper的分布式锁。
Redis的分布式锁实现过程:
1.使用jedis的2.7.x及以上版本。
2.获取锁:
命令:SET key value [NX|XX] [EX|PX] seconds
NX – 只有键key不存在的时候才会设置key的值
XX – 只有键key存在的时候才会设置key的值
EX seconds – 设置键key的过期时间,单位时秒
PX milliseconds – 设置键key的过期时间,单位时毫秒
3.释放锁
需要注意的地方:
①这个锁必须要设置一个过期时间。
②第一步获取锁的操作,把它实现成了两个Redis命令,SETNX+EXPIRE。虽然这两个命令和前面描述中的一个SET命令执行效果相同,但却不是原子的。如果客户端在执行完SETNX后崩溃了,那么就没有机会执行EXPIRE了,导致它一直持有这个锁。
③设置一个随机字符串randomVal是很有必要的,它保证了一个客户端释放的锁必须是自己持有的那个锁。
释放锁的操作必须使用Lua脚本来实现。释放锁其实包含三步操作:GET、判断和DEL,用Lua脚本来实现能保证这三步的原子性。
7.Redis主从复制读写分离
单机redis具有以下问题:(1) 如果机器出现故障,将导致redis不可用。(2) 单机的容量不能扩展。(3) 单机的OPS瓶颈不能很好解决
主从复制的作用:(1) 数据副本:多一份数据副本,保证redis高可用。(2) 扩展性能:如容量、QPS等.
主从复制的特点:(1) 一个master可以有多个slave 。(2) 一个slave只能有一个master 。(3) 数据流向是单向的,master到slave。
可能遇到的问题:(1) 复制数据的延迟:例如从节点发生阻塞,就会到值复制数据的延迟。(2) 读到过期的数据。(3) 从节点也有可能发生故障。
8.数据分片模型
Redis的分片技术是指将数据分散到多个Redis实例中的方法,分片之后,每个redis拥有一部分原数据集的子集。在数据量非常大时,这种技术能够将数据量分散到若干主机的redis实例上,进而减轻单台redis实例的压力。
分片的优点:
分片技术能够以更易扩展的方式使用多台计算机的存储能力(这里主要指内存的存储能力)和计算能力:
- 从存储能力的角度,分片技术通过使用多台计算机的内存来承担更大量的数据,如果没有分片技术,那么redis的存储能力将受限于单台主机的内存大小。
- 从计算能力的角度,分片技术通过将计算任务分散到多核或者多台主机中,能够充分利用多核、多台主机的计算能力。
分片的缺陷:
- 通常无法支持涉及多键的操作;在redis中有很多一次操作多个key的操作,例如求集合交集的SINTER操作,该操作将涉及到多个键,而这多个键有可能被分片到不同的redis实例中,此时就无法执行这种操作。
- Redis的事务操作中涉及多个键时也不能用。
- 分片将导致数据处理更加复杂;例如在分片过程中,随着redis实例的增加,数据备份等操作都将会变得更加复杂。
- Redis目前不支持动态分片操作,扩容和缩容操作都会比较复杂,尤其分片操作部署在客户端时,需要重新配置和启动客户端。在使用过程中缩容用的不多,扩容可以采用后面介绍的预分片策略来缓解此问题。
分片部署方式一般分为以下三种:
- 在客户端做分片:这种方式在客户端确定要连接的redis实例,然后直接访问相应的redis实例。
- 在代理中做分片:这种方式中,客户端并不直接访问redis实例,它也不知道自己要访问的具体是哪个redis实例,而是由代理转发请求和结果。其工作过程为:客户端先将请求发送给代理,代理通过分片算法确定要访问的是哪个redis实例,然后将请求发送给相应的redis实例,redis实例将结果返回给代理,代理最后将结果返回给客户端。
在redis服务器端做分片:这种方式被称为“查询路由”,在这种方式中客户端随机选择一个redis实例发送请求,如果所请求的内容不再当前redis实例中它会负责将请求转交给正确的redis实例。或者,redis实例不会转发请求,而是将正确redis的信息发给客户端,由客户端再去向正确的redis实例发送请求。
9.Redis的内存回收策略
Redis内存回收机制主要体现在以下两个方面:(1) 删除到达时间的键对象。(2) 内存使用达到maxmemory上限时触发内存溢出控制策略。
1.redis采用惰性删除和定时任务删除机制实现过期键的内存回收。
惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。
定时任务删除:Redis内部维护一个定时任务,默认每秒运行10次(通过配置hz控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例,使用快慢两种速率模式回收键。
2.内存溢出控制策略
- noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的Key。(推荐)
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个Key。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的Key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个Key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的Key优先移除。
10.Redis相比Memcached有哪些优势
(1)memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型。
(2)redis的速度比memcached快很多。
(3)redis可以持久化其数据。
(4)Redis支持数据的备份,即master-slave模式的数据备份。
(5)value大小:redis最大可以达到1GB,而memcached只有1MB。
(6)使用底层模型不同,它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
11.Redis常见性能问题和解决方案
(1)Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2)如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3)为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4)尽量避免在压力很大的主库上增加从库
(5)主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
12.Redis持久化机制
Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。
1.RDB持久化
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。
优点:①RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。②Redis加载RDB恢复数据远远快于AOF的方式。
缺点:①RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。②RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式 的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。
2.AOF持久化
AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。
优点:如果不考虑性能,AOF可以最大限度保证数据完整性,可以设置每发生一次写操作就调用一次fsync函数;更加灵活,可以使用不同的fsync策略,完全不使用fsync,每秒使用fsync(默认),以及每次查询时使用fsync。
缺点:与RDB方式相比,相同数据集大小AOF占用空间更大;若调用fsync的频率过快,性能会变差。
13.什么是缓存穿透、缓存击穿和缓存雪崩以及如何处理
1.缓存穿透:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决办法:
(1)接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
(2)从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
2.缓存击穿:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决办法:
(1)设置热点数据永远不过期。
(2)加互斥锁。
3.缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决办法:
(1)缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
(2)如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
(3)设置热点数据永远不过期。
14.Redis最适合的场景
1.redis由于数据的读取和操作都在内存当中操作,读写的效率较高,所以经常被用来做数据的缓存。把一些需要频繁访问的数据,而且在短时间之内不会发生变化的,放入redis中进行操作。从而提高用户的请求速度和降低网站的负载,降低数据库的读写次数,就把这些数据放到缓存中。
2.一些常用的实时计算的功能。需要实时变化和展示的功能,就可以把相关数据放在redis中进行操作。大大提高效率。
3.消息队列,经常用来构建类似实时聊天系统的功能,大大提高应用的可用性。