心灵鸡汤
不攀不比,心淡然;不怒不嗔,心随和。不艾,不怨,心坦然。生活,有苦乐,人生,有起落。学会挥袖从容。乐观,那不是没烦恼,而是要懂得知足;人生无完美,曲折亦风景,看开,想通,就是完美
# 一:非关系型数据库和关系型数据库的对比
非关系型数据库优点:
成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用, 相比关系型数据库价格便宜
查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及 nosql数据库
存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型 以及对象或者是集合等各种格式,而数据库则只支持基础类型
扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难
非关系型数据库缺点:
维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语
不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本
不提供关系型数据库对事务的处理
非关系型数据库的优势:
性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高
可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展
关系型数据库的优势:
复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询
事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱 势,反之亦然
关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的 时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据
# 二:什么是Redis
Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。
Redis 支持数据的备份,即 master-slave 模式的数据备份。
Redis 优势:
性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s 。
丰富的数据类型 – Redis 支持二进制案例的 Strings, Lists, Hashes, Sets 及Ordered Sets 数据类型操作。
原子 – Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC指令包起来。
丰富的特性 – Redis 还支持 publish/subscribe, 通知, key 过期等等特性。
具体查看 Redis 前言
# 三:Redis的应用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)
- 聊天室的在线好友列表
- 任务队列。(秒杀、抢购、12306等等)
- 应用排行榜
- 网站访问统计
- 数据过期处理(可以精确到毫秒)
- 分布式集群架构中的session分离
具体查看 Redis 前言 - 使用场景
# 四:Redis支持的键值数据类型有哪些?
数据类型 | 可以存储的值 | 底层实现 | 操作 | 应用场景 |
---|---|---|---|---|
STRING | 字符串、整数或者浮点数 | long,long double,SDS动态字符串 | 从两端压入或者弹出元素对单个或者多个元素进行修剪,只保留一个范围内的元素 | 做简单的键值对缓存 |
LIST | 列表 | ziplist(压缩列表),LinkedList(链表) | 从两端压入或者弹出元素对单个或者多个元素进行修剪,只保留一个范围内的元素 | 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据 |
HASH | 包含键值对的无序散列表 | ziplist,hashtable(字典) | 添加、获取、移除单个键值对获取所有键值对检查某个键是否存在 | 结构化的数据,比如一个对象 |
SET | 无序集合 | intset(整数集合),hashtable | 添加、获取、移除单个元素检查一个元素是否存在于集合中计算交集、并集、差集从集合里面随机获取元素 | 交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集 |
ZSET | 有序集合 | ziplist,skiplist(跳跃表) | 添加、获取、删除元素根据分值范围或者成员来获取元素计算一个键的排名 | 去重但可以排序,如获取排名前几名的用户 |
具体查看 Redis 基础数据结构 与 Redis 基本内部结构
# 五:Redis持久化如何实现
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不 会因为故障而丢失,这种机制就是 Redis 的持久化机制
Redis 的持久化机制有两种:
RDB 快照
AOF 日志
快照是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。
具体查看 Redis 持久化
# 六:Redis持久化实现方式对比
RDB 模式
RDB 模式持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
RDB的几个优点:
与AOF方式相比,通过rdb文件恢复数据比较快。
rdb文件非常紧凑,适合于数据备份。
通过RDB进行数据备,由于使用子进程生成,所以对Redis服务器性能影响较小。
RDB的几个缺点:
如果服务器宕机的话,采用RDB的方式会造成某个时段内数据的丢失,比如我们设置10分钟同步一次或5分 钟达到1000次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。
使用save命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
使用bgsave命令在forks子进程时,如果数据量太大,forks的过程也会发生阻塞,另外,forks子进程会 耗费内存。
AOF 模式
AOF(append only file)持久化:以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令 达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
AOF 模式在配置文件中可以通过appendfsync选项指定写入策略,有三个选项
always 客户端的每一个写操作都保存到aof文件当中,这种策略很安全,但是每个写请求都有IO操作,所以也很慢。
everysec appendfsync的默认写入策略,每秒写入一次aof文件,因此,最多可能会丢失1s的数据。
no Redis服务器不负责写入aof,而是交由操作系统来处理什么时候写入aof文件。更快,但也是最不安 全的选择,不推荐使用。
AOF的优点:
- AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少。
AOF的缺点:
AOF方式生成的日志文件太大,即使通过AFO重写,文件体积仍然很大。
恢复数据的速度比RDB慢。
方式 | RDB | AOF |
---|---|---|
启动优化级 | 低 | 高 |
体积 | 小 | 打 |
恢复速度 | 快 | 慢 |
数据安全性 | 会丢数据 | 由策略决定 |
轻量 | 重 | 轻 |
当RDB与AOF两种方式都开启时,Redis会优先使用AOF日志来恢复数据,因为AOF保存的文件比RDB文件更完整。
具体查看 Redis 持久化
# 七:Redis 缓存失效策略和主键失效机制
作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略.在Redis当中,有生存期的key被称为volatile。在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),它可能会被删除。
影响生存时间的一些操作生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,也就是说,修改key对应的value和使用另外相同的key和value来覆盖以后,当前数据的生存时间不同。比如说,对一个 key 执行INCR命令,对一个列表进行LPUSH命令,或者对一个哈希表执行HSET命令,这类操作都不会修改 key 本身的生存时间。另一方面,如果使用RENAME对一个 key 进行改名,那么改名后的 key的生存时间和改名前一样。RENAME命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。使用PERSIST命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个persistent key 。
如何更新生存时间可以对一个已经带有生存时间的 key 执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。过期时间的精度已经被控制在1ms之内,主键失效的时间复杂度是O(1),
EXPIRE和TTL命令搭配使用,TTL可以查看key的当前生存时间。设置成功返回 1;当 key 不存在或者不能为 key 设置生存时间时,返回 0
最大缓存配置
在 redis 中,允许用户设置最大使用内存大小server.maxmemory,默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。
redis 提供 6 种数据淘汰策略:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据
注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略
使用策略规则:
- 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
- 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
# 八:一个字符串类型的值最大容量
512M
具体查看 Redis 基本内部结构
# 九:redis 过期键的删除策略
定时删除:在设置键的过期时间的同时,创建一个定时器 timer,让定时器在键的过期时间来定时,立即执行对键的删除操作。
惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
# 十:为什么 Redis 需要把所有数据放到内存中?
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度会严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
# 十一:Redis 是单进程单线程的?
Redis 是单进程单线程的,Redis 利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销
# 十二:keys和scan
具体查看 Redis 遍历
# 十三:如果有大量的 key 需要设置同一时间过期,一般需要注意什么?
如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
# 十四:什么是缓存雪崩?解决方案是什么?
缓存雪崩是指:缓存在同一时刻出现大面积的过期,导致应该访问缓存的请求都去查询数据库了,从而对数据库CPU和内存造成巨大压力,严重的会导致数据库宕机,从而形成一系列的连锁反应,导致整个系统崩溃。
解决方案
当并发量不高的时候,加锁排队
缓存数据过期的时间设置随机,防止同一时间缓存大量过期
# 十五:什么是缓存穿透?解决方案是什么?
缓存穿透是指请求查询缓存和数据库中都没有的数据,导致所有请求都落在数据库上,造成数据库短时间承受大量请求而崩掉。
解决方案
业务层增加校验逻辑,不符合规范的查询直接拦截
当一个数据在缓存和数据库都没有的时候,可以设置一个key-null的键值对在缓存中,设置比较短过期时间,比如30s。
使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层数据库的压力。
布隆过滤器
布隆过滤器的作用是可以判断一个元素是否存在
它会采用多个不同的哈希函数来将一个元素映射到 bitmap 中。当需要判断一个元素是否存在时,只需要通过这些哈希函数对元素进行映射,如果映射的位置都存在,则可以判断该元素存在;反之只要有一个哈希函数的映射不存在,则该元素不存在
# 十七:什么是缓存预热?解决方案是什么?
缓存预热就是在系统上线之后,将相关缓存的数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再更新缓存。用户可以直接查询缓存中预热的数据。
解决方案
- 定时刷新缓存
- 上线后手动操作
# 十八:什么是缓存降级?
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级
缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
- 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
- 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
- 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
- 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
# 十九:Redis事务的概念
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
具体可查看 Redis 事务
# 二十:Redis事务的三个阶段
- 事务开始 MULTI
- 命令入队
- 事务执行 EXEC
具体可查看 Redis 事务
# 二十一:Redis事务相关命令
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
如果在一个事务中的命令出现错误,那么所有的命令都不会执行(这句话的意思是指在MULTI之后将命令加入到是命令队列的过程中,某条命令出现语法错误,则这次事务不会执行)
如果在一个事务中出现运行错误,那么正确的命令会被执行。(这句话是指在命令已经全部加入到命令队列之后,执行EXEC命令开始执行命令队列中的命令时,发生错误,错误的命令不会被执行,而正确的命令依旧还是会被执行)
1. WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令
2. MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行
3. EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil
4. 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出
5. UNWATCH命令可以取消watch对所有key的监控
# 二十二:热点Key
# 二十三:Redis异步队列怎么用?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
如果对方追问可不可以不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
如果对方追问能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
如果对方追问pub/sub有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
如果对方追问redis如何实现延时队列?我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。
# 二十四:Pipeline 好处
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目
具体可以查看 pipeline
# 二十五:Redis集群原理
- Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务
- Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储
# 二十六:Redis 和 Memecache 区别
- memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
- redis的速度比memcached快很多
- redis可以持久化其数据
# 二十七:Redis 支持的 java 客户端都有哪些?
Redisson、Jedis、lettuce等等,官方推荐使用Redisson
# 二十八:Jedis 和 Redisson 区别
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持。
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
# 二十九:怎么保证缓存和数据库数据的一致性?
- 合理设置缓存的过期时间
- 新增、更改、删除数据库操作时同步更新 Redis,可以使用事物机制来保证数据的一致性
# 三十:Redis 分布式锁
Redis 分布式锁其实就是在系统里面占一个 "坑",其他程序也要占 "坑" 的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。
占坑一般使用 setnx(set if not exists) 指令,只允许被一个程序占有,使用完调用 del 释放锁。
具体可以查看 Redis 分布式锁
# 三十一:Redis 分布式锁有什么缺陷?
Redis 分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题
# 三十二:Redis 如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。
# 三十三:Redis 常见的性能问题有哪些?该如何解决?
- 主服务器写内存快照,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以主服务器最好不要写内存快照。
- Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,主从库最好在同一个局域网内。