redis为什么这么快?
- 内存存储:redis使用内存存储,没有磁盘IO上的开销;
- 单线程实现:单线程处理请求,避免了多个线程之间切换和锁资源竞争的开销;
- 非阻塞IO:redis使用多路复用IO技术,在poll、epoll选择最优IO实现;
- 优化的数据结构:redis有诸多可以直接应用的优化数据结构实现,应用层可以直接使用原生的数据结构提升性能;
常见数据结构?
- string
- list
- hash
- set
- zset
- 发布订阅
- GEO
- HyperLogLog
底层数据结构有哪些?
- 简单动态字符串
- 双端链表
- 跳跃表
- 字典
- 压缩列表
- 整数集合
不同的数据类型使用的业务场景?
- list:消息队列
- zset:延迟消息队列、最新消息排行、带权重的消息队列
- hash:商品属性、账户信息
- set:共同好友、排行榜
- Hyperloglog:基数计算器
redis持久化?
RDB:将redis数据根据配置同步到磁盘上【二进制压缩文件】
1、同步是可能会影响redis性能,突然redis异常,会有段数据没有得到保存
2、同步的两个命令SAVE(会阻塞redis)、BGSAVE(fork一个子进程异步处理)
3、每次重启检查是否有rdb文件,会进行载入工作
AOF:将redis执行的有关数据操作命令同步到磁盘上
1、同步的文件会越来越大
2、可以通过重写方式重新写入aof文件
3、过程:命令追加-》文件写入-》文件同步
4、appendfsync : always[存在及同步]|everysec[隔一段时间进行同步]|no[系统决定何时同步]
5、内容是redis通信协议(RESP)格式的命令文本存储
比较:
1、aof文件比rdb更新频率高,优先使用aof还原数据
2、aof比rdb更安全也更大
3、rdb性能比aof好
分布式锁
setnx + expire
set nx ex
如果有大量key需要设置同一时间过期,一般要注意什么?
1、redis层面 过期删除方面考虑
可能出现redis卡顿现象
2、针对缓存后面的DB 通过缓存
可能出现缓存雪崩
解决方案:一般需要在时间加上一个随机值,使得过期时间分散一些
redis淘汰策略配置?
- volatile-random:设置有效期的key 随机清理
- volatile-lru:设置有效期的key 最少使用清理
- volatile-ttl:设置有效期的key 即将过期的清理
- allkeys-lru::所有key 最少使用清理
- allkeys-random:所有key 随机清理
- no-enviction:不做清理【默认策略】
实现一下LRU?
基于哈希链表的思路实现
哈希表查找快,但是数据没有固定顺序;链表有顺序之分,插入、删除快,但是查找慢;所以结合一下,形成一种新得数据结构:哈希链表LinkedHashMap
算法—leetcode—146. LRU 缓存机制 (juejin.cn)
redis内存占用满了会如何?
默认淘汰策略:对于写请求不在提供服务,直接返回错误
如果配置了淘汰策略:当内存达到上线,会冲刷掉旧的内容
删除过期key的策略?
1、定时删除【给key创建一个定时器,时间到了即可以删除,消耗CPU】
2、惰性删除【针对已经过期的key在,在访问的时候会删除,消耗内存】
3、定期删除【根据服务器设置时间定期处理已经过期的key】
假设redis有10亿个key,其中有10W个key是以某个固定的前缀开头的,如何将他们全部造出来?
考察点:单进程单线程模式,如果一个命令执行的慢,会影响接下来的其他命令
1、如果在测试环境,没有访问压力的情况可是用keys 模式匹配搜索
2、如果在线上环境可以使用scan命令,无阻塞增量的命令搜索指定前缀的key,可能存在重复的,需要程序进行筛选
redis比memcache的优势?
- 支持多种数据类型
- 支持持久化
- 支持数据备份,即master-slave模式的数据备份
redis集群如何做?都用哪些方案?
-
客户端分片
-
基于代理的分片
1、twemproxy: 使用代理方式
问题:1、自身单端口实例的压力、针对redis集群数量改变是,数据无法移动到新的节点
2、codis
- 路由查询
1、edis3.0自带redis-cluster:特点是分布式hash算法不是一致性hash,而是hash曹方式
一致性哈希?
并发竞争问题?
redis为单进程单线程模式,采用队列模式将“并发访问”变成“串行访问”,因此对于多个客户端连接并不存在竞争,一般此类问题都是由客户端连接造成的
解决方案
1、保证每个客户端正常有序的与redis进行通信,对连接进行池化
2、服务器角度,利用setnx实现锁机制
redis有哪些架构模式?
单机版
特点:简单
问题:1、内存容量有限 2、处理能力有限,3、无法高可用
主从复制
特点:1、master/slave角色,2、master/slave数据相同 3降低master读取压力
问题:1、无法保证高可用 2、没有解决master写的压力
哨兵
特点:完善监控保证高可用,监控各个节点动态提醒、自动故障转移,
缺点:1、主动模式,切换需要时间可能丢失数据 2、没有解决master写的压力
集群
twemproxy代理模式
特点:
1、支持多种hash算法, 2、失败节点自动删除 3、后端sharding分片逻辑对业务透明
redis通信协议?
redis服务器与客户端通过RESP(redis Serialization Protocol)协议通信【文本协议】
优点:简单的实现,快速的解析,直观理解
redis是否支持事务?
【ACID 原子性,一致性,隔离性,持久性】
原子性:数据库将事务中多个操作当做一个整体来执行,服务要么执行事务中的所有操作,要么一个操作也不执行
因为redis记录日志的时间问题,不支持操作的回滚
一致性:数据库在执行事务之前是一致的,那么在事务执行之后无论事务是否成功,数据库也应该是一致的
两个方面查看一致性:一个执行错误是否确保一致性,另一个宕机时,是否确保一致性机制
隔离性:数据库中有多个事务并发执行,各个事务之间不会相互影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同
redis因为是单线程操作,所以在隔离性上有天生的隔离机制;redis执行事务时,不会对事务进行中断,所以redis事务总是以串行的方式运行,事务也是具备隔离性
持久性:当一个事务执行完毕,执行这个事务所得到的结果被保存在持久化的存储中,即使服务器在事务执行完成后停机了,执行事务的结果也不会被丢失纯内存运行,不具备持久化
RDB持久化模式,取决于RDB策略,只有满足策略才会执行Bgsave,异步执行不能保证redis持久化;
AOF持久化模式,只有将appendfsync设置为always,程序才会在执行命令同步保存到磁盘,redis才具备持久性
开启事务:multi
执行命令
提交事务:exec
结论
redis 具备了一定原子性,但不支持回滚
redis不具备“一致性”概念
redis具备隔离性
redis通过一定策略可以保证持久性
Redis设计更多的是追求简单和高性能,不会受制于传统ACID束缚
查看redis使用情况的命令
命令 | 说明 |
---|---|
info server | 服务器信息 |
info clients | 已连接客户端信息 |
info memory | 查看内存信息 |
info persistence | 查看rdb 、aof的相关信息 |
info stats | 一般统计信息 |
info replication | 主/从复制信息 |
info cpu | cpu计算量统计信息 |
info commandstats | redis命令统计信息 |
redis cluster | redis集群信息 |
info keyspace | 数据库相关统计信息 |
info all | 返回所有信息 |
info default | 返回默认设置信息 |
缓存穿透?
访问一个不存在的key,缓存不起作用,请求就会穿透到DB,流量大时DB就会出问题
解决方案:
1、采用布隆过滤器,使用一个足够大的bitmap,用户存储可能访问的key,不存在的key直接过滤掉;【类似白名单】
2、访问key未在DB查询到值,也将控制写进缓存,单可以设置较短过期时间;【全量缓存】
缓存击穿?
一个存在的key,在缓存过期的同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案:
1、在访问key之前,采用setnx来设置一个短期key来锁住当前key的访问
缓存雪崩?
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大,压力骤增,引起雪崩;
解决方案:
1、可以给缓存设置过期时间加一个随机值时间,使得每个key的过期时间分不开来,不会集中到同一时刻失效;
2、做二级缓存,A1作为原始缓存,A2作为拷贝缓存,A1失效时,可以直接访问A2;A1缓存失效 时间设置短期,A2设置为长期;
3、在缓存失效后,可以通过加锁或者队列来控制读数据库写缓存的线程数量。
redis中底层是如何实现的?如何解决冲突和扩容?
底层使用的数据编码
当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
- 哈希对象保存的键值对数量小于512个;不能满足这两个条件的哈希对象需要使用hashtable编码。
解决哈希冲突
Redis的哈希表使用链地址法(separate chaining)来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来,这就解决了键冲突的问题。
扩展与收缩
哈希表的扩展与收缩当以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:
- 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
- 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。
redis的操作为什么是原子性的,如何保证原子性?
Redis的操作之所以是原子性的,是因为Redis是*单线程的。
线程是操作系统能够进行运算调度的最小单元。它被包含在进程之中,是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。