简约版八股文(day2)
Redis(内存中 -> 非关系型数据库)
redis 是什么,为什么要用 redis
redis 是基于键值对的 NoSQL 数据库,经常用来做缓存
用户直接读取数据库中的数据效率是相对比较慢的,如果把数据读取后放到缓存中,下次就可以直接在缓存中读取数据,读取缓存的数据效率要远大于在磁盘中读取数据。
直接操作缓存能够承受的请求是远远大于直接访问数据库的
使用场景: 消息队列 (命令) 缓存 锁
redis 过期策略
惰性删除:客户端访问一个 key 的时候,redis 先检查它的过期时间,如果发现过期立即删除这个 key
定期删除:redis 默认每隔 100ms 随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除
定期清理的两种模式:
- SLOW 模式是定时任务,执行频率默认为 10hz,每次不超过 25ms,以通过修改配置文件 redis.conf 的 hz 选项来调整这个次数
- FAST 模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于 2ms,每次耗时不超过 1ms
Redis 数据淘汰策略
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
**MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据? **
答:可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据
redis 持久化机制(怎么保证 redis 挂掉后再重启数据可以恢复)
redis 持久化机制有两种:
RDB 持久化:RDB 是一个二进制的快照文件(恢复快),它是把 redis 内存存储的数据写到磁盘上,当 redis 实例宕机恢复数据的时候,方便从 RDB 的快照文件中恢复数据。
AOF 持久化:AOF 的含义是追加文件(丢数据的风险要小),当 redis 操作写命令的时候,都会存储这个文件中,当 redis 实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据.
当 Redis 崩溃导致数据丢失时,可以根据不同的持久化方式和集群配置来恢复数据。以下是一般情况下的恢复步骤:
【了解】Redis 的数据恢复步骤(两种持久化方式 + 集群)
前期准备:
- 检查 Redis 的持久化配置,确定是否启用了持久化机制。
中期操作:
2. 如果启用了 RDB 持久化:
- 检查是否存在最近一次成功生成的 RDB 文件。
- 将该 RDB 文件复制到 Redis 服务器的数据目录下。
- 重启 Redis 服务器,它将加载并恢复 RDB 文件中的数据。
- 如果启用了 AOF 持久化:
- 检查是否存在最近一次成功写入的 AOF 文件。
- 编辑 Redis 配置文件,将
appendonly
配置项设置为yes
,以确保在启动时加载 AOF 文件。- 重启 Redis 服务器,它将通过重播 AOF 文件中的写操作来恢复数据。
后期处理:
4. 对于集群环境:
- 如果使用 Redis Cluster 进行数据分片和复制,当一个节点崩溃时,其他正常运行的节点会自动接管失效节点的槽位和数据,从而保证数据的可用性。
- 验证和同步:
- 一旦 Redis 服务器启动并加载了持久化文件,需要进行数据验证和同步。
- 验证恢复后的数据是否与原始数据一致,确保数据的完整性。
- 如果存在其他数据源,如关系型数据库,可以考虑重新加载数据或进行数据同步,以恢复可能丢失的数据。
缓存穿透、缓存击穿、缓存雪崩解决方案
- 缓存穿透:指查询一个不存在的数据,由于不会存储不存在的数据,所有每次查询都会直接查询数据库,导致缓存没有发挥作用并可能导致 DB 挂掉。解决方案:
使用布隆过滤器,在缓存查询前,先对数据进行过期,如果数据不存在,则查询数据库并将结果放到缓存中查询返回数据为空,仍把这个数据进行缓存,但过期时间较短
布隆过滤器主要是用于检索一个元素是否在一个集合中。
它的底层主要是先去初始化一个比较大数组,里面存放的二进制 0 或 1。在一开始都是 0,当一个 key 来了之后经过 3 次 hash 计算,模于数组长度找到数据的下标然后把数组中原来的 0 改为 1,这样的话,三个数组的位置就能标明一个 key 的存在。查找的过程也是一样的。
- 缓存击穿:指一个热点 key 突然过期,恰好这个时间点有大量并发请求来请求这个数据,此时由于数据过期,这些请求就会直接请求到数据库,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。解决方案:
- 使用互斥锁,为数据设置互斥锁,保证同一时间只有一个线程访问数据库
- 设置当前 key 逻辑过期
- 在设置key 的时候,设置一个过期时间字段一块存入缓存中,不给当前 key 设置过期时间
- 当查询的时候,从 redis 取出数据后判断时间是否过期
- 如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新
当然两种方案各有利弊:
如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题
如果选择 key 的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据同步这块做不到强一致。
3.缓存雪崩:设置缓存时采用相同的过期时间,导致缓存在某一时刻同时失效,请求全部到数据库,导致数据库崩溃。与缓存击穿的区别:雪崩是很多 key,击穿是某一个 key 缓存。解决方案:
将缓存失效时间散开,比如可以在原有的失效时间上加一个随机时间值
Redis 集群方案
redis 并发竞争 key 问题
多个客户端同时对同一个 key 进行读写操作
解决方案:
使用 redis 事务,事务是多个命令作为一个整体进行操作,使用 redis 事务可以保证多个客户端对一个 key 进行操作时,不会发生并发竞争问题。
使用 redis 锁(乐观锁悲观锁),在修改一个 key 时,先将其锁定,然后执行修改操作,操作完成后,释放锁
redis 和 memcached 的区别
1.redis 支持更丰富的数据类型,redis 提供 list,set,zset,hash 等数据结构的存储,memcached 支持简单的数据类型:string
2.redis 支持持久化
3.memcached 没有原生的集群模式,redis 支持 cluster 集群模式
4.memcached 是多线程,非阻塞 IO 复用的网络模型。redis 使用单线程的多路 IO 复用模型
MongDB(内存和硬盘中读写 -> 介于非关系型数据库和关系数据库)
为什么要使用 mongdb? 使用场景?
使用 MongoDB 的原因:
- 灵活的数据模型:MongoDB 是一种面向文档的数据库,可以存储各种类型的数据,而不需要事先定义固定的表结构。这种灵活的数据模型使得 MongoDB 适用于快速迭代和需求变化频繁的应用场景。
- 高性能和可扩展性:MongoDB 支持水平扩展,可以通过添加更多的节点来增加系统的存储容量和吞吐量。它具有高性能的查询和索引功能,适合处理大量数据和高并发读写的场景。
- 高可用性和自动故障转移:MongoDB 提供了副本集(Replica Set)机制,通过复制数据到多个节点实现数据的冗余存储和故障恢复。当主节点发生故障时,副本集会自动选举新的主节点,确保系统的可用性和数据的安全性。
- 地理分布式存储:MongoDB 支持分片(Sharding)机制,可以将数据分散存储在多个物理节点上,实现数据的地理分布式存储。这使得 MongoDB 适用于跨地理位置的多数据中心应用,可以提供更好的性能和用户体验。
MongoDB 适用于以下场景:
- Web 应用和内容管理:MongoDB 的灵活性和高性能使其成为构建 Web 应用和内容管理系统的理想选择。它可以存储各种类型的数据,如用户信息、文章、日志等,并支持复杂的查询和索引操作。
- 实时分析和大数据处理:MongoDB 的分布式架构和高并发读写能力使其适用于实时分析和大数据处理场景。它可以处理大量的数据并支持复杂的聚合操作,提供快速的数据分析和查询能力。
- 物联网(IoT)和传感器数据:MongoDB 的扩展性和高可用性使其成为存储和处理物联网和传感器数据的理想选择。它可以处理大量的实时数据并支持时序数据的存储和查询。
- 实时推送和即时通讯:MongoDB 的低延迟读写和副本集机制使其适用于实时推送和即时通讯应用。它可以存储和处理用户的实时消息,并支持快速的数据更新和查询。
为什么会数据丢失?(MongoDB 丢数据的事情,已经成为传说了)
MongoDB 数据丢失的原因可能包括硬件故障、网络问题、软件错误、人为操作失误和数据库故障。
Web 开发
cookie 和 session 的区别
1. 存储位置不同:cookie 存放在客户端,session 存放在服务端
2. 存储容量不同:单个 cookie 保存的数据小于等于 4kb,一个站点最多保存 20 个 cookie,而 session 没有上限
3. 存储方式不同:cookie 只能存储 ASCLL 字符串,因此无法存储复制的数据类型,如对象。session 可以存储任意类型数据
4. 隐私策略不同:cookie 中的数据是对客户端可见的,session 的数据存储在服务器,对客户端是透明的
5. 生命周期不同:可以通过设置 cookie 的属性,让 cookie 长期有效,session 默认失效时间 20 分钟,服务器把长期没有
活动的 session 清除
6. 对服务器的压力:cookie 保存在客户端,不占用服务器资源,session 保存在服务器,数量多的话会对造成服务器压力
7. 跨域支持不同:cookie 支持跨域访问,session 不支持
拦截器和过滤器的区别
拦截器是基于 Java 反射机制和代理模式实现的,过滤器是基于 Java 过滤器接口实现的
拦截器处理顺序在处理链前面,即请求被接收前,过滤器处理顺序是处理链后面
拦截器通常对请求进行修改,验证,记录等,需要对请求进行全面的处理,过滤器需要对请求进行转换,压缩,安全认证等
拦截器通常用于实现安全认证,权限认证,日志记录等,过滤器通常用于实现数据压缩,字符编码转换,放在 xss 攻击等
消息队列
消息队列有什么用
消息队列 MQ 一般用来解决应用解耦,异步处理、流量削峰等问题。
应用解耦:消息队列可以避免模块之间的相互调用,将消息放到消息队列中,有需要的模块可以在直接 MQ 中调用,减少了模块之间的耦合
异步处理:消息队列的异步处理机制,可以在很多不需要立即处理的消息中使用,比如发送短信。
流量削峰:Mysql 不能处理大量的高并发请求,所以在流量高峰期,请求直接到 MySQL 的话可能导致 MySQL 挂掉,但高峰期的流量并不会一直存在,所以如果以加服务器的方式来抗压,会导致资源的浪费,所以可以将用户的请求放到消息队列,在随后的时间里平滑的处理这些请求
消息被重复消费怎么解决
1. 消息唯一标识:在消息中添加唯一标识,每次消费消息时先检查该标识是否消费过,如果已经消费则忽略消息,可以通过消息体中添加唯一标识或者使用 rabbitmq 自带的消息 ID 来实现
2. 消息确认机制:消费者成功处理消息后,将消息从队列中删除或标记为已处理
3. 消息持久化:将消息持久化到磁盘中,避免消息丢失或者重复发送
__EOF__