redis
1.为什么说redis快?
- 完全基于内存
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,也不存在加锁和释放锁的操作。
- 使用多路I/O复用模型,非阻塞IO。
- 使用底层模型不同。Redis自己有VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
2.多路I/O复用模型
“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
利用select、poll、epoll可以同时监察多个流的I/O事件的能力,在空闲的时候,会把当前阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态唤醒,于是程序就会轮询一遍所有的流(epoll只轮询那些真正发出了事件的流),并且只依次顺序处理就绪的流。
2.1 select
select(int nfds, fd_set *r, fd_set *w,fd_set *e, struct timeval *timeout)
- nfds:r、w、e集合中标识符最大值加一。
- r:读集合
- w:写集合
- e:异常集合
- timeout:表示等待时间。如果设置为null,表示如果没有事件时,可以一直处于阻塞状态;否则,会根据指定的时间进行阻塞。
问题:
- 大小限制(FD_SETSIZE,1024)
- 在每次返回时都会被内核修改,需要重新设置
- 在读写事件比较少的时候,依然需要对所有的集合进行扫描
2.2 poll
poll(struct pollfd *fds, int nfds, inttimeout)
struct pollfd {
int fd;
short events;
short revents;
}
3.redis实现乐观锁?
- watch
- multi
- exec
4.哨兵机制
- 每个哨兵进程以每秒钟依次的频率向整个集群的Master主服务器,Slave从服务器以及其他Sentinl进程发送依次PING命令。
- 如果一个实例距离最后一次有效回复PING命令的时间超过down-after-milliseconds选项设定的值,则这个实例会被哨兵进程标记为主观下线(SDOWN)。
- 如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有哨兵
5.缓存穿透和缓存雪崩
- 缓存穿透:指查询大量不在缓存中的数据,会直接请求数据库。
解决方法:
1.采用布隆过滤器,拦截肯定不存在的数据
2.为不存在的数据设置空值
- 缓存雪崩:大量数据缓存同时过期。
解决方法:
1.加锁排队
2.在原来过期时间添加一个随机时间
6.sorted set 底层实现?
- 当数据较少时,sorted set是由一个ziplist来实现的。
- 当数据较多的时候,sorted set是由一个dict + 一个skiplist来实现的。dict用来查询数据到分数的对应关系,而skiplist用来根据分数查询数据。
** skiplist和平衡树、哈希表的比较**
- skiplist和平衡树的元素是有序排列的,而哈希表不是。
- skiplist的范围查询要优于平衡树。平衡树需要先找到范围的最小值,然后通过中序遍历的方式继续寻找;而skiplist在找到最小值后,只需要对最低层的链表进行遍历。
- 平衡树的插入和删除操作可能会引起子树的调整,而skiplist只需要修改相邻节点的指针,相对简单。
- 查找key的时候,skiplist和平衡树的时间复杂度都是O(logn),而哈希表是O(1)。
7.redis持久化
RDB和AOF
由于Redis是内存数据库,它将自己的数据库状态存储在内存中,如果不想办法将存储在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。
RDB
- SAVE:会阻塞Redis服务器进程,直到RDB文件创建完成为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
- BGSAVE:会派生出一个子进程,然后由子进程负责创建RDB文件,父进程进行处理命令请求。
一旦BGSAVE开始执行了,SAVE命令和BGSAVE命令都会被服务器拒绝,避免产生竞争条件。如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行;如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。避免产生大量的磁盘写入操作。
将某段时间内的所有数据持久化到磁盘中,类似快照的行为。当在进行持久化的过程时有数据的更新,会把这些记录保存到备份文件中,最后会把备份文件拿来替换原文件。
缺点:如果机器发生故障,容易丢失某个时间段内的数据。
AOF
将所有写命令保存到AOF缓冲区中,根据appendfsync的值(默认为everysec)来对AOF文件同步,由于大量的写入命令会导致AOF文件过大,后台就会开启一个子进程对原AOF文件进行重写(合并命令),对文件进行压缩。如果在重写期间执行了写入命令,会将写入命令保存到AOF重写缓冲区中,等到AOF重写结束,再将AOF重写缓冲区中的内容追加到新AOF文件中,此时会对父进程阻塞,最后用新AOF文件替换原AOF文件。
- 每秒fsync一次(默认)
- 每次有新命令追加到AOF文件是就执行一次fsync
- 从不
缺点:由于恢复要进行的操作较多,可能会导致主线程阻塞。
8. redis zset底层为啥用skiplist而不用跳表?
跳表:采用多层链表结构,查询、删除、插入时间复杂度都是O(logn)。
在操作时间复杂度差不多的情况下,skiplist的实现简单。
9. redis 查询某一前缀的key
方法一:利用keys查找(不推荐)
keys pattern:查找所有符合给定模式pattern的key
通过这种方式查询可能会导致查询时间过长,导致redis其他服务卡顿。
方法二:利用scan查找(推荐)
scan cursor [MATCH pattern] [COUNT count]
- 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程;
- 以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历;
- 不保证每次执行都返回某个给定数量的元素,支持模糊查询;
- 一次返回的数量不可控,只能是大概率符合count参数。
keys命令的时间复杂度为O(n),而scan命令会将遍历操作分解成m次时间复杂度为O(1)的操作来执行,从而解决使用keys命令遍历大量数据而导致服务器阻塞的情况。
10. redis过期删除策略
- 定时删除:在设置键的过期时间的时候创建一个定时器,当过期时间到的时候立马执行删除操作。对CPU不是很友好。
- 惰性删除:惰性删除不会在键过期的时候立马删除,而是当外部命令获取这个键的时候才会主动删除。过程为:接收get执行、判断是否过期、执行删除操作、返回nil。
- 定期删除:每隔一段时间检测是否有过期键,如果有执行删除操作。
11. 一致性hash算法
hash算法依赖于现有的机器数目,导致所有的位置都要发生变动。一致性hash算法是采用与2^32取模,形成一个虚拟的圆环,当机器数目发生变动的时候,只有少量的位置变动。由于可能存在数据倾斜问题,引入虚拟节点来使数据分布均匀,只是多了虚拟节点到实际节点的映射。
12. 使用场景?
- 缓存
- 计数(String)
- 消息队列(list)
- 求共同部分(set)
- 估算访问量(hyperloglog)
- 过滤请求(bitmap)
- 排行榜(sorted set)
- session服务器