RedisBasic

Redis是什么?

Redis是一个开源的,基于内存的,也可进行持久化的,基于C语言编写的键值对存储数据库。

Redis过期key是怎么样清理的?

(1)惰性清除

在访问key时,如果发现key已经过期,那么会将key删除。

(2)定时清理

Redis配置项hz定义了serverCron任务的执行周期,默认每次清理时间为25ms,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。

(3)内存不够时清理

当执行写入命令时,如果发现内存不够,那么就会按照配置的淘汰策略清理内存,淘汰策略主要由以下几种:

1.noeviction,不删除,达到内存限制时,执行写入命令时直接返回错误信息。

2.allkeys-lru,在所有key中,优先删除最少使用的key。(适合请求符合幂定律分布,也就是一些键访问频繁,一部分键访问较少)

3.allkeys-random,在所有key中,随机删除一部分key。(适合请求分布比较平均)

4.volatile-lru,在设置了expire的key中,优先删除最少使用的key。

5.volatile-random,在设置了expire的key中,随机删除一部分key。

6.volatile-ttl,在设置了expire的key中,优先删除剩余时间段的key。

4.0版本后增加以下两种:

volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。

allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key。

LRU算法

LRU算法的设计原则是如果一个数据近期没有被访问到,那么之后一段时间都不会被访问到。所以当元素个数达到限制的值时,优先移除距离上次使用时间最久的元素。

可以使用HashMap<String, Node>+双向链表Node来实现,每次访问元素后,将元素移动到链表尾部,当元素满了时,将链表头部的元素移除。
使用单向链表能不能实现呢,也可以,单向链表的节点虽然获取不到pre节点的信息,但是可以将下一个节点的key和value设置在当前节点上,然后把当前节点的next指针指向下下个节点。

LFU算法

LFU算法的设计原则时,如果一个数据在最近一段时间被访问的时次数越多,那么之后被访问的概率会越大,实现是每个数据
都有一个引用计数,每次数据被访问后,引用计数加1,需要淘汰数据时,淘汰引用计数最小的数据。在Redis的实现中,
每次key被访问后,引用计数是加一个介于0到1之间的数p,并且访问越频繁p值越大,而且在一定的时间间隔内,
如果key没有被访问,引用计数会减少。

Redis为什么是单线程的?

Redis官方FAQ回答:

Redis是基于内存的操作,CPU不会成为瓶颈所在,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
(这里的单线程指的是处理网络请求的模块是单线程,其他模块不一定是单线程的)

Redis采用单线程的优势:

1.Redis项目的代码会更加清晰,处理逻辑会更加简单。

2.不用考虑多个线程修改数据的情况,修改数据时不用加锁,解锁,也不会出现死锁的问题,导致性能消耗。

3.不存在多进程或者多线程导致的切换而造成的一些性能消耗。

Redis采用单线程的劣势:

1.无法充分发挥多核机器的优势,不过可以通过在机器上启动多个Redis实例来利用资源。

Redis的性能为什么这么高?

根据官网的介绍,Redis单机可以到到10W的QPS(每秒处理请求数),Redis这么快的原因主要有以下几点:

1.完全基于内存,数据全部存储在内存中,读取时没有磁盘IO,所以速度非常快。

2.Redis采用单线程的模型,没有上下文切换的开销,也没有竞态条件,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

3.Redis项目中使用的数据结构都是专门设计的,例如SDS(简单动态字符串)是对C语言中的字符串频繁修改时,会频繁地进行内存分配,十分消耗性能,而SDS会使用空间预分配和惰性空间释放来避免这些问题的出现。
空间预分配技术: 对SDS进行修改时,如果修改后SDS实际使用长度为len,

当len<1M时,分配的空间会是2*len+1,也就是会预留len长度的未使用空间,其中1存储空字符

当len>1M时,分配的空间会是len+1+1M,也就是会预留1M长度的未使用空间,其中1存储空字符

4.采用多路复用IO模型,可以同时监测多个流的IO事件能力,在空闲时,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态唤醒,轮询那些真正发出了事件的流,并只依次顺序的处理就绪的流。可以让单个线程高效的处理多个连接请求(尽量减少网络 I/O 的时间消耗)。

Linux中IO模型一共有哪些?

IO模型主要由阻塞式I/O模型,非阻塞式I/O模型,I/O复用模型,信息驱动式I/O模型,异步I/O模型。

阻塞式I/O模型

用户态进程调用recvfrom系统调用来接受数据,如果当前内核中数据没有准备好,那么会一直等待,不会进行其他操作,直到内核中的数据准备好,将数据拷贝到用户空间内存,然后recvfrom返回成功的信号,此时用户态进行才解除阻塞的状态,处理收到的数据。

非阻塞时I/O模型

在非阻塞式I/O模型中,当进程等待内核的数据,而当该数据未到达的时候,进程会不断询问内核,直到内核准备好数据。
用户态进程调用recvfrom接收数据,当前并没有数据报文产生,此时recvfrom返回EWOULDBLOCK,用户态进程会一直调用recvfrom询问内核,待内核准备好数据的时候,之后用户态进程不再询问内核,待数据从内核复制到用户空间,recvfrom成功返回,用户态进程开始处理数据。

I/O多路复用模型

I/O复用指的是多个I/O连接复用一个进程。
最初级的I/O复用,就是一个进程对应多个连接,每次从头至尾进行遍历,判断是否有I/O事件需要处理,有的话就进行处理,缺点是效率比较低,如果一直没有事件进来,会导致CPU空转。
升级版的I/O复用模型
当没有I/O事件时,进程处于阻塞状态,当有I/O事件时,就会有一个代理去唤醒进程,去进行轮询,来处理I/O事件。(这里的代理也就是select和poll,select只能观察1024个连接,poll可以观察无限个连接)
epoll是对select和poll的升级版,解决了很多问题,是线程安全的,而且可以通知进程是哪个Socket连接有I/O事件,提高了查找效率。
epoll和select/poll最大区别是
(1)epoll内部使用了mmap共享了用户和内核的部分空间,避免了数据的来回拷贝
(2)epoll基于事件驱动,epoll_wait只返回发生的事件避免了像select和poll对事件的整个轮寻操作。

信息驱动式I/O模型

是非阻塞的,当需要等待数据时,用户态进程会给内核发送一个信号,告知自己需要的数据,然后就去执行其他任务了,内核准备好数据后,会给用户态发送一个信号,用户态进程收到之后,会立马调用recvfrom,等待数据从从内核空间复制到用户空间,待完成之后recvfrom返回成功指示,用户态进程才处理数据。

异步I/O模型

与信息驱动式I/O模型区别在于,是在数据从内核态拷贝到用户空间之后,内核才通知用户态进程来处理数据。在复制数据到用户空间这个时间段内,用户态进程也是不阻塞的。

同步与异步的区别是什么?

同步与异步的区别在于调用结果的通知方式上。
同步执行一个方法后,需要等待结果返回,然后继续执行下去。
异步执行一个方法后,不会等待结果的返回,调用方定时主动去轮询调用结果或者被调用方在执行完成后通过回调来通知调用方。

阻塞与非阻塞的区别是什么?

阻塞与非阻塞的区别在于进程/线程在等待消息时,进程/线程是否是挂起状态。

阻塞调用

在消息发出去后,消息返回之前,当前进程/线程会被挂起,直到有消息返回,当前进/线程才会被激活。

非阻塞调用

在消息发出去后,不会阻塞当前进/线程,而会立即返回,可以去执行其他任务。

如何解决Redis缓存穿透问题?

Redis 缓存穿透指的是攻击者故意大量请求一些Redis缓存中不存在key的数据,导致请
求打到数据库上,导致数据库压力过大。

解决方案如下:

1.做好参数校验,无效的请求直接返回,只能避免一部分情况,攻击者总是可以找到一些没有覆盖的情况。

2.对缓存中找不到的key,需要去数据库查找的key,缓存到Redis中,但是可能会导致Redis中缓存大量无效的key,可以设置一个很短的过期时间,例如1分钟。

3.也可以使用布隆过滤器,将所有可能的存在的数据通过去hash值的方式存入到一个足够大的bitmap中去,处理请求时,通过在botmap中查找,可以将不存在的数据拦截掉。

如何解决Redis缓存雪崩问题?

缓存雪崩主要指的是短时间内大量key失效,导致所有请求全部转向数据库,导致数据库压力过大。

解决方案:

1.在给缓存设置失效时间时加一个随机值,避免集体失效。

2.双缓存机制,缓存A的失效时间为20分钟,缓存B没有失效时间,从缓存A读取数据,缓存A中没有时,去缓存B中读取数据,并且启动一个异步线程来更新缓存A。