OSI七层模型
物理层:将帧中的比特传送到下一个节点(比特)
数据链路层:将数据包装成帧并传送到路径上下一个节点,将相邻节点间不可靠的物理链路变成可靠的逻辑链路(帧)
网络层:路由寻址,连接主机,将数据报从一台主机发送到另一台主机(数据报,分组)
运输层:实现端到端的通信,在应用程序端传送报文段(报文段)
会话层:建立、管理、维护会话。
表示层:数据格式的转换,编码解码,加密解密,压缩解压缩。
应用层:端系统中应用程序交换报文通信(报文)
应用层有哪些协议
http、https、DNS、SNMP、SMTP、POP3、IMAP、FTP、SSH、Telnet
DNS解析过程,根域名在上还是顶级域名在上
在DNS解析过程中,根域名位于顶级域名的上方。DNS(域名系统)的解析过程是一个从根域名开始,逐级向下查询到最终域名所对应IP地址的过程
TCP三次握手,为什么要第三次握手
客户端发送SYN=1,seq=x的连接请求报文。同步已发送。
服务端发送SYN=1,seq=y,ACK=1,ack=x+1的连接确认报文。同步已接收。
客户端发送ACK=1,seq=x+1,ack=y+1的确认报文。建立连接。
防止滞留的第一次报文到达服务器端而导致不必要的错误和资源浪费。
防止服务端一直等待,浪费资源。
四次挥手过程,为什么要有四次
客户端发送FIN=1,seq=u的连接释放报文。进入FIN-WAIT-1
服务端发送ACK=1,seq=v,ack=u+1的确认报文。进入CLOSE-WAIT
服务端数据发送完毕后,发送FIN=1,seq=w,ACK=1,ack=u+1的连接释放报文。进入LAST-ACK
客户端发送ACK=1,seq=u+1,ack=w+1的确认报文,并等待2MSL。进入TIME-WAIT
服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序:
Ping原理
- 发送ICMP Echo请求:
- Ping程序会向目标主机发送一个ICMP Echo请求(Echo Request)数据包。这个数据包是一个ICMP消息,用于询问目标主机是否可达。
- ICMP Echo请求数据包包含了Ping命令的源IP地址和目标IP地址,以及一些其他控制信息(如序列号、时间戳等)。
- 等待ICMP Echo回应:
- 目标主机在收到ICMP Echo请求后,会处理这个请求,并返回一个ICMP Echo回应(Echo Reply)数据包给Ping程序的源主机。
- 如果目标主机不可达或未响应,Ping程序则不会收到ICMP Echo回应。
进程通信方式哪几种,管道单向还是双向,有什么缺点
- 管道(Pipe):
- 类型:管道分为匿名管道和命名管道。
- 特点:管道数据只能单向流动,因此要实现双向通信需要创建两个管道。匿名管道主要用于父子进程之间的通信,而命名管道则可以在不相关的进程之间使用。
- 缺点:
- 管道只能支持单向数据流,实现双向通信需要额外的资源。
- 管道通常具有固定大小的缓冲区,当缓冲区满时,写入操作会被阻塞,直到有足够的空间可用于写入数据;同样,当缓冲区为空时,读取操作会被阻塞,直到有数据可用于读取。
- 管道传输的数据是无格式的流且大小受限,不适合传输大量数据或复杂数据结构。
- 信号(Signal):
- 特点:信号是进程之间唯一的异步通信机制,用于通知进程某个事件的发生。
- 缺点:信号传递的信息量较少,主要用于通知进程某个事件,而不适合传输大量数据或复杂信息。
- 信号量(Semaphore):
- 特点:信号量是一个计数器,用于控制多个进程对共享资源的访问,防止资源冲突。
- 缺点:相比于其他通信方式,使用信号量可能会增加系统的复杂性,并需要谨慎处理以避免死锁和资源泄漏。
- 消息队列(Message Queue):
- 特点:消息队列允许进程以消息的形式发送和接收数据,支持多对多的通信方式。
- 缺点:相比于管道和共享内存,消息队列的实现和管理可能更复杂,且在某些情况下可能不是最高效的通信方式。
- 共享内存(Shared Memory):
- 特点:共享内存允许多个进程直接访问同一块内存区域,是进程间通信中最快的一种方式。
- 缺点:需要处理同步和互斥问题以避免数据竞争和死锁。此外,对共享内存的访问需要特别小心以确保数据的一致性和安全性。
- Socket:
- 特点:Socket不仅可用于本地进程通信,还可用于不同主机之间的进程通信。它支持TCP和UDP等多种协议,适用于不同的通信需求。
- 缺点:相比于其他本地进程通信方式,Socket的实现和管理可能更复杂,并需要考虑网络延迟和数据包丢失等问题。
进程调度算法
先来先服务(FCFS)调度算法
- 定义:按照进程到达的先后顺序进行服务,即先来先执行。
- 特点:
- 算法简单,但效率低。
- 对长进程有利,但对短进程不利。
- 适用于CPU繁忙型作业,而不利于I/O繁忙型作业。
2. 短作业优先(SJF)调度算法
- 定义:优先处理预计运行时间短的进程。
- 特点:
- 能够有效降低进程的平均等待时间与周转时间,提高系统吞吐量。
- 对长进程不利,可能导致长进程饥饿。
- 进程长短由用户估计,不一定准确。
3. 优先级调度算法
- 定义:根据进程的优先级来分配资源,优先级高的进程优先获得资源(执行)。
- 分类:
- 非抢占式:一旦进程开始执行,就不会因为其他进程的到来而中断。
- 抢占式:当有更高优先级的进程到来时,正在执行的进程会被中断,优先执行高优先级的进程。
- 静态优先级:在创建进程时确定,并在进程的整个运行期间保持不变。
- 动态优先级:根据进程的行为(如等待时间的长短)而发生动态变化。
4. 时间片轮转(RR)调度算法
- 定义:将CPU时间分割成一系列的时间片,每个进程被分配一个时间片,在该时间片中,进程可以执行其代码。
- 特点:
- 简单且公平,每个进程都有平等的执行机会。
- 易于实现,适合于单处理器环境。
- 可以有效地控制CPU使用率,提高系统响应速度。
- 在高并发情况下,进程切换的开销可能导致效率下降。
5. 多级反馈队列(MFQ)调度算法
- 定义:建立多个优先权不同的就绪队列,每个队列有自己的调度算法和时间片。
- 特点:
- 优先权越高的队列中,进程时间片就越小。
- 新进程首先放入第一队列的末尾,若未在一个时间片内完成,则转入下一队列的末尾。
- 仅当高优先级队列为空时,才调度低优先级队列中的进程。
- 是一种较好的进程调度算法,能够兼顾长短作业,同时有较好的响应时间。
6. 最高响应比优先(HRRN)调度算法
- 定义:根据进程的响应比(R = (w + s) / s,其中w为等待时间,s为预计服务时间)来选择进程。
- 特点:
- 权衡了短作业和长作业,避免了长作业饥饿的问题。
- 需要估计进程的预计服务时间,存在一定的不准确性。
死锁条件
互斥、不可剥夺、保持申请,循环等待
怎么解除死锁
一次性申请所有的资源(破坏保持申请)
按序申请资源(破坏循环等待)
占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源(破坏不可剥夺)
Mysql事务特性
ACID
原子性:一个事务的所有操作,要么全部完成,要么全部不完成,不会结束在某个中间环节。通过undo日志保证
持久性:一个事务,一旦提交,对数据库的修改就是永久的,即使数据库故障也不会丢失。通过redo日志保证
隔离性:数据库允许多个并发事务同时对其数据进行读取和修改的能力,隔离性可以防止多个事务由于交叉执行而导致的不一致性。通过MVCC机制(快照读)或加悲观锁的当前读保证。
一致性:事务操作前后,数据满足完整性约束,数据库保持一致性状态。通过原子性,持久性,隔离性保证
怎么实现RR隔离级别,介绍一下MVCC
可重复读(REPEATABLE READ)
MySQL 通过多版本并发控制(MVCC)来实现这一级别。
这可以防止脏读和不可重复读,但可能出现幻读。(在一个事务内多次查询记录的数量,如果出现前后两次查询到的记录数不一样的情况,就意味着发生了幻读)
MVCC 的实现依赖于:Read View、undo log。
每个事务启动时,系统会为其分配一个唯一的事务ID。
当一个事务要访问数据库中的某个数据时,系统会检查该数据的版本号和事务的启动时间。如果某个数据行有多个版本,事务会选择不晚于其开始时间的最新版本,确保事务只读取在它开始之前已经存在的数据。
当一个事务修改某个数据时,系统会为该数据创建一个新版本号,并将修改后的数据存储在一个新的位置。同时,旧版本的数据仍然可用供其他事务访问。
explain查看哪些字段,有什么作用
select_type:查询类型,包括simple、primary等
type:访问类型
all,全表扫描
index,全索引扫描
range,索引范围扫描
ref,非唯一索引扫描
eq_ref,唯一索引扫描
const,结果只有一条数据
system,引擎统计好的一条数据
extra:额外信息
Using filesort :当查询语句中包含 group by 操作,而且无法利用索引完成排序操作的时候, 这时不得不选择相应的排序算法进行,甚至可能会通过文件排序,效率是很低的,所以要避免这种问题的出现。
Using index:所需数据只需在索引即可全部获得,不须要再到表中取数据,也就是使用了覆盖索引
using where:回表查询
b+树和b树区别,有什么优势
B+树更适用于范围查询:B+树的所有数据记录都存储在叶子节点上,并通过链表连接,使得范围查询操作更加高效。而B树需要在整棵树上进行遍历,效率较低。
B+树较少的磁盘IO次数:由于B+树的非叶子节点仅存储索引,数据记录存储在叶子节点上,并且通过链表连接,减少了磁盘IO次数,提高了数据访问的效率。
B+树查询性能更稳定:B树:查询时只需找到匹配元素即可,可能在根节点就找到,也可能在最底层的叶子节点找到,性能不稳定。B+树:查询时必须查找到叶子节点,虽然这看起来增加了查询步骤,但实际上由于B+树的稳定结构,使得查询性能更加稳定。
索引失效的情况
使用!=、<>、NOT IN、NOT EXISTS
使用函数、运算符、类型转换。MySQL 要会自动把字符串转为数字,相对于使用CAST 函数,和下面运算符和使用函数等情况类似。因为索引保存的是索引字段的原始值,而不是表达式计算后的值,所以无法走索引,只能通过把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式。
使用OR。SELECT * FROM `user` WHERE `name` = '张三' OR height = '175';在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效
开头模糊查询。SELECT * FROM `user` WHERE `name` LIKE '%冰';因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较。
不满足最左匹配原则
ES怎么分页索引
elasticsearch中通过修改
from
、size
参数来控制要返回的分页结果:
from
:从第几个文档开始
size
:总共查询几个文档类似于mysql中的
limit ?, ?
语法如下:
GET /items/_search { "query": { "match_all": {} }, "from": 0, // 分页开始的位置,默认为0 "size": 10, // 每页文档数量,默认10 "sort": [ { "price": { "order": "desc" } } ] }
如果是深分页,直接限制。或
search after
:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
Redis常见数据类型
hash、list、set、zset、string
string常用的redis指令
SET:添加或者修改已经存在的一个String类型的键值对。语法为
SET key value [EX seconds] [PX milliseconds] [NX|XX]
,其中EX
和PX
用于设置键的过期时间(秒或毫秒),NX
表示只在键不存在时设置,XX
表示只在键已存在时设置。GET:根据key获取String类型的value。如果键不存在,则返回nil。
APPEND:将指定的值追加到已存在键的值末尾。如果键不存在,则相当于SET命令。
STRLEN:返回指定键对应的字符串值的长度。
用户信息用什么数据类型
Hash
你redis存的数据过期时间多久
预热,永久
排行榜用什么
zset
缓存雪崩、缓存穿透、缓存击穿
缓存雪崩
缓存雪崩是指当缓存中的大量数据在同一时间过期或者redis宕机,导致大量的请求直接打到数据库上,从而使得数据库压力骤增,甚至可能导致数据库宕机。
解决方案:
均匀过期或不过期:避免大量缓存同时过期,可以为每个缓存项设置一个随机的过期时间。或提前预热,设置秒杀前不过期。
使用互斥锁:当缓存失效时,使用互斥锁保证只有一个请求去查询数据库,然后更新缓存,其他请求则等待缓存更新完成。
构建redis集群:通过Redis Sentinel或Redis Cluster等方案,提高缓存系统的可用性,减少单点故障的风险。
依赖隔离组件为使用后端限流熔断并降级。 比如使用Sentinel或Hystrix限流降级组件。
设置二级缓存。二级缓存指的是除了 Redis 本身的缓存,再设置一层缓存,当 Redis 失效之后,先去查询二级缓存。例如可以设置一个本地缓存,在 Redis 缓存失效的时候先去查询本地缓存而非查询数据库。
缓存击穿
缓存击穿是指缓存中没有,但是数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。解决方案:
1、对于热点的key可以设置永不过期的key。热点key也可以提前预热,加载进redis并设置秒杀之前不过期。
2、使用分布式锁。如果缓存不存在数据,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求
3、逻辑过期:如果查到缓存是旧数据,尝试获取互斥锁锁开启缓存重建线程,如果获取锁失败,返回旧数据。性能好,但不保证一致性,实现比较复杂。
4、接口限流,熔断与降级 重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。
缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存中也没有这个数据,导致每次请求都会直接打到数据库上。攻击者可能会利用这个漏洞进行恶意查询,导致数据库压力增大。
解决方案:
缓存空对象:与缓存击穿类似,当查询不存在的数据时,将空结果或默认值放入缓存中,并设置较短的过期时间。当数据库更新该数据时,再将redis数据刷新。
使用布隆过滤器:在缓存之前添加一个布隆过滤器来过滤不存在的数据请求。
主要由一个很长的二进制向量和一系列随机映射函数构成。它主要用于检索一个元素是否在一个集合中,具有高效的插入和查询特性。它的存储空间和插入/查询时间都是常数O(k),其中k为哈希函数的个数。布隆过滤器不存在,绝对不存在,布隆过滤器存在,有可能不存在。一般情况下不能从布隆过滤器中删除元素。我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。
做好接口限流与熔断:对恶意请求进行限制和熔断,防止过多无效请求打到数据库上。
如果说缓存不存在,那么就通过布隆过滤器进行初步筛选,然后判断是否存在缓存空值,如果存在直接返回失败。如果不存在缓存空值,使用锁机制避免多个相同请求同时访问数据库。最后,如果请求数据库为空,那么将为空的 Key 进行空对象值缓存。
旁路缓存策略,缓存不一致的情况
比较适合读请求比较多的场景。
需要同时维系 db 和 cache,并且是以 db 的结果为准。
这种策略将数据以数据库中的数据为准,缓存中的数据是按需加载的,从而确保数据的一致性。这种策略可能导致缓存中存在脏数据
更新数据时,先更新数据库中的数据,然后再从缓存中将对应的数据删除。
读取数据时先从缓存中查找数据。如果缓存中存在所需数据,则直接从缓存中获取并返回给用户;
如果缓存中没有数据,则从数据库中读取,并将读取到的数据放入缓存中。
理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多。
请求 1 从 db 读数据 A-> 请求 2 更新 db 中的数据 A(此时缓存中无数据 A ,故不用执行删除缓存操作 ) -> 请求 1 将数据 A 写入 cache。出现不一致。
如果删除失败,可以采用删除重试。使用MQ来实现异步操作。
哈希表记录一下已走过的路,创建一个方向数组和一个当前方向,每次碰壁或者前面以及走过,进行90°掉头,这可以通过当前方向加一再取模实现。当时代码也是这样,就是输出很奇怪,估计是没加bool mp[15][15]的初始化导致的。
vector<int> spiralorder(vector<vector<int>>& matrix) {bool mp[15][15]={false};int m = matrix.size();if (m == 0) {return {};}vector<int> arr;int n=matrix[0].size();int d[4][2]={{0,1},{1,0},{0,-1},{-1,0}};int index=0;int x=0,y=0,cnt=0;while(cnt<m*n){arr.push_back(matrix[x][y]);mp[x][y] = true;int xx = x + d[index][0], yy = y + d[index][1];if (xx >= 0 && xx < m && yy >= 0 && yy < n && mp[xx][yy] == false) {x = xx;y = yy;} else {index = (index + 1) % 4;int xx = x + d[index][0], yy = y + d[index][1];x = xx;y = yy;}// cout << x << " " << y << " "<<index<<endl;cnt+=1;}return arr;
}