Grafana 的数据显示会五分钟自动补全。当向 Prometheus 中插入某个时间戳的值时,其值会延续五分钟。
K8S 中的 Sidecar 模式:通常情况下一个 Pod 只包含一个容器,但是 Sidecar 模式是指为主容器提供额外功能(例如监控) 从而将其他容器加入到同一个 Pod 中。再例如 Istio 实现 Sidecar 自动注入。
Federated cluster,联邦集群
限流算法:漏桶和令牌桶算法,漏桶算法处理请求的速度固定,突发请求过多时会丢弃;令牌桶算法除了限制数据的平均传输速率外,还要求允许某种程度的突发传输。
常见 HTTP 状态码:2XX,成功响应;3XX,重定向消息;4XX,客户端错误响应;5XX,服务端错误响应。
请求分为四部分:请求行,请求头,空行,请求正文(不一定有)。
.gitignore
是在文件从工作区被 add 到暂存区时判断是否要被忽略,对于已经在暂存区的文件,不作判断。
数据结构
红黑树特性
- 节点非红即黑
- 根节点是黑色
- 叶子节点是黑色的空节点
- 红节点的子节点是黑节点
- 从根节点到空节点的每条路径,包含相同数量的黑色节点
常见NoSQL数据库类型
- 键值数据库:Redis
- 列式数据库:HBase, ClickHouse,适用于联机分析处理(OLAP)场景,数仓等
- 文档数据库:MongoDB, ElasticSearch
- 图数据库
Clickhouse:
- 不支持事务:因为面向列。不存在隔离级别。ClickHouse的定位是分析性数据库,而不是严格的关系型数据库
- 不支持高并发
- 可处理大量读请求,数据压缩的特性
分布式理论
两阶段协议
https://juejin.cn/post/6844903621495095309
三阶段协议
https://juejin.cn/post/6844903621495111688
FLP 不可能性原理
在网络可靠,存在节点失效(即便只有一个)的最小化异步模型系统中,不存在一个可以解决一致性问题的确定性算法。对于允许节点失效情况下,纯粹异步系统无法确保一致性在有限时间内完成。
举例:三个人在不同房间,进行投票(投票结果是 0 或者 1)。三个人彼此可以通过电话进行沟通,但经常会有人时不时地睡着。比如某个时候,A 投票 0,B 投票 1,C 收到了两人的投票,然后 C 睡着了。A 和 B 则永远无法在有限时间内获知最终的结果。
CAP 定理
分布式系统只能交付以下三个所需特性中的两个特性:一致性、可用性和分区容错性。
- C(Consistency):一致性,指的是所有客户端可以同时看到相同的数据。每当数据写入一个节点时,就必须立即将其转发或复制到系统中的所有其他节点
- A(Availability):可用性,指的是可用性表示发出数据请求的任何客户端都会得到响应,即使一个或多个节点宕机。
- P(Partition tolerance):分区容错性,指分区之间的通信可能失败。
一般来说,在分布式系统中不可避免会出现分区,因此认为 P 总是成立。因此若要实现一致性(CP),那么当出现分区问题时,就需要舍弃不一致的节点,即牺牲可用性。同理,若要实现可用性(AP),那么当出现分区问题时,就无法保证一致性。
Nacos集群默认支持 AP 原则(即不支持数据一致性,支持服务注册的临时实例),但也可切换至基于 Raft的 CP 原则(支持服务注册的永久实例)。
临时实例和持久化实例的区别:
- 持久化实例健康检查后会被标记为不健康,而临时实例会直接从列表中删除。
MongoDB 是一种 CP 数据存储——它通过牺牲可用性、保持一致性来解决网络分区问题。
BASE 理论
- Basically Available(基本可用),假设系统出现了不可预知的故障,但还是能用。
- Soft State(软状态),允许系统在多个不同节点的数据副本存在数据延时。
- Eventually Consistent(最终一致性),在软状态的一定期限过后,应达到数据的最终一致性。
核心思想是:即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
Raft 算法
三种角色:
- Leader,发送心跳包以维护自己的Leader状态
- Follower,响应Leader发送的心跳包
- Candidate,如果没有接收到Leader的心跳信号后,Follower会变成Candidate并向其他节点发送拉票信息
记时器:
- 选举超时时间(Follower拥有):若超时前没有收到心跳包,认为Leader下线,此时Follower会变成Candidate;但反之如果接收到心跳包,则重置计时器。
- 投票超时时间(Candidate拥有):若超时前没有得到半数以上的票数,则竞选失败;反之成功。
- 竞选等待超时时间(竞选失败的Follower拥有):竞选失败的Follower需要等待一段时间后才能重新竞选。
脑裂
由于某种原因(网络不稳定)使得主从集群一分为二,导致master或者leader节点的数量不为1(0或2)。
- redis脑裂
master 机器突然脱离网络,使得 sentinel 集群无法感知到 master 的存在,会重新选举一个 master 节点。网络恢复后则会存在两个 master 节点。
- zookeeper脑裂
脑裂可能出现平票的情况,从而无法选举出 leader。因此,zk 中建议节点数是奇数。
K8s
Kubernetes主要由以下几个核心组件组成:
- etcd保存了整个集群的状态;
- apiserver提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
- controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
- scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;
- kubelet负责维持容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理;
- Container runtime负责镜像管理以及Pod和容器的真正运行(CRI);
- kube-proxy负责为Service提供cluster内部的服务发现和负载均衡;
除了核心组件,还有一些推荐的add-ons(扩展):
- kube-dns负责为整个集群提供DNS服务
- Ingress Controller为服务提供外网入口
- Heapster提供资源监控
- Dashboard提供GUI
- Federation提供跨可用区的集群
- Fluentd-elasticsearch提供集群日志采集、存储与查询
23种设计模式与六大原则
23种设计模式
- 创建型
- 单例模式(Singleton)
- 饿汉模式:类加载的时候就创建实例
- 懒汉模式:只有当第一次使用的时候才创建实例
- 双重校验锁:线程安全,实例需要用
volatile
修饰
- 原型模式(Prototype),能够复制已有对象,而又无需使代码依赖它们所属的类。例如
Cloneable
接口是立即可用的原型模式。 - 工厂方法模式(Factory Method),在父类中提供一个创建对象的方法, 允许子类决定实例化对象
- 抽象工厂模式(Abstract Factory),定义了用于创建不同产品的接口, 但将实际的创建工作留给了具体工厂类。 每个工厂类型都对应一个特定的产品变体。
- 建造者模式(Builder),分步骤创建复杂对象。
- 单例模式(Singleton)
- 行为型
- 模板方法模式(Template Method),它在基类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
- 责任链模式(Chain of Responsibility),它让多个处理器(对象节点)按顺序处理该请求,直到其中某个处理成功为止,例如检查商品模块,需要先检查商品合法性,再检查商品可见性等等。
- 命令模式(Command)
- 迭代器模式(Iterator),能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
- 中介者模式(Mediator)
- 备忘录模式(Memeoto)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy),规定每个策略的不同方法,只需选择不同的策略即可执行不同的方法,例如数字人项目中 Provider 的选择(1:AiLab,2:Creatify,3:IC,4:IC-NEW)
- 访问者模式(Visitor)
- 解释器模式(Interpreter)
- 结构型
- 适配器模式(Adaptor)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰模式(Decorator)
- 外观模式(Facade),为复杂系统、程序库或框架提供一个简单的接口。通常作用于整个对象子系统上。
- 享元模式(Flyweight),共享多个对象的部分状态将内存消耗最小化。
- 代理模式(Proxy),例如动态代理
六大原则
- 开闭原则,对扩展开放,对修改关闭。
- 单一职责原则,顾名思义,一个类的职责只有有一个。
- 里氏替换原则,子类可以扩展父类的功能,但不能改变父类原有的功能。
- 依赖倒转原则,依赖于抽象,不能依赖于具体实现(面向接口编程)。
- 接口隔离原则,类之间的依赖关系应该建立在最小的接口上。
- 迪米特原则,一个软件实体应当尽可能少的与其他实体发生相互作用。
分布式事务 Seata
分两阶段提交
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
执行流程:
- TM 向 TC 请求发起(Begin)、提交(Commit)、回滚(Rollback)等全局事务。
- TM 把代表全局事务的XID绑定到分支事务上。
- RM 向 TC 注册,把分支事务关联到XID代表的全局事务中。
- RM 把分支事务的执行结果上报给 TC。
- TC 发送分支提交(Branch Commit)或分支回滚(Branch Rollback)命令给RM。
四大模式:
- AT模式,能适用于大部分事务情况。
- XA模式
- SAGA模式,核心思想是将长事务拆分成多个本地短事务。
- TCC模式,核心思想是针对每个操作都要注册一个与其对应的确认和补偿操作。
其他分布式事务解决方案:
- 基于 RocketMQ 的消息事务
- 二阶段提交
- 三阶段提交,CanCommit、PreCommit、DoCommit三阶段
ZooKeeper(CP)
使用 ZAB 协议,包括两种运行模式:
消息广播(Leader 正常运行),所有事务请求由单一主进程处理,Leader 转换为事务 Proposal 并广播分发给 Follower,Leader 等待 Follower 的反馈,超过半数的 Follower 反馈消息后,Leader 再发送 Commit 消息提交事务 Proposal。
崩溃恢复(Leader 不可用时),新选举产生的 Leader 与过半的 Follower 进行同步,使数据一致,同步后进入消息广播模式。
主要服务于分布式系统,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理,用来解决分布式集群中应用系统的一致性问题,构建 ZooKeeper 集群时使用的服务器最好是奇数台。其设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
数据结构:Znode节点,与Unix文件系统类似,通过路径来标识,例如 /home/app
,Znode节点又分为两种类型:临时节点、持久节点、临时顺序节点、持久顺序节点。客户端和服务端断开连接后,临时节点会自动删除但持久节点不会。分布式锁使用的是临时节点。
Watcher 监听器:节点的数据发生变化后会通知到节点的监听器。
基本命令:
- create : 在树中的某个位置创建一个节点
- delete : 删除一个节点存在:测试节点是否存在于某个位置
- get data : 从节点读取数据
- set data: 将数据写入节点
- get children : 检索节点的子节点列表
- sync : 等待数据被传播
实现分布式锁原理:判断能否创建临时节点,如果不能则监听父节点(非公平锁)或上一个节点(公平锁),任务执行完成后释放该节点并通知所有监听的节点。
实现注册中心原理:初始化时 Provider 先向目录写入 URL 地址,Consumer 订阅相同目录的 URL 地址和自己的 URL 地址,监控中心订阅 Provider 和 Consumer 的 URL 地址;Consumer 在第一次调用服务时,会通过注册中心找到相应的服务的IP地址列表,并缓存到本地,以供后续使用;当 Provider 下线时,会在列表中移除 URL 并将新的 URL 地址发送给 Consumer 并缓存至本地,服务上线也是一样的。
与 Nacos 的区别:
Nacos 是 AP 的,保证可用性,每个节点都是平等的,若干个节点 crash 后不会影响正常节点,但查到的信息不一定是最新的。ZK 在 crash 后进行 leader 选举,期间是不可用的。
微服务框架:Dubbo
底层基于 Netty 的 NIO 框架,基于 TCP 协议传输
四种负载均衡策略
支持服务端服务级别、服务端方法级别、客户端服务级别、客户端方法级别的负载均衡配置。还可以拓展负载均衡算法。
- 随机负载均衡,默认策略
- 轮询负载均衡
- 最少活跃调用数,每个 Provider 都有一个计数器,开始调用则计数器加一,结束调用计数器减一。原则是将请求分配给处理速度快的 Provider。
- 一致性哈希负载均衡
HTTPS 如何保证可靠传输
TLS 协议:
- 对称加密/非对称加密
- 数字证书
- 三次握手/四次挥手
TLS 的 rsa 握手过程,存在什么安全隐患,怎么解决的?
- 安全隐患:不支持前向保密。如果私钥泄露了,所有密文都会被破解
- 解决方案:密钥协商算法
HTTPS 绝对安全吗
NO,可以通过中间人攻击的方式,即所有请求先发送给第三方(中间人),返回中间人的证书,后续的请求/响应都通过中间人进行转发。
HTTP版本的演变
HTTP/1.0
- 无状态:服务器不追踪不记录请求过的状态
- 无连接:每次请求要建立连接,无法复用连接;在前一个请求响应到达之后下一个请求才能发送,如果前一个阻塞,后面的请求也被阻塞
HTTP/1.1
默认浏览器对同一域名的并发请求限制为 6 - 8 个。
- 长连接:默认保持长连接,数据传输完成后只要不断开连接,就可以继续传输数据
- 管道化:基于上面长连接的基础,可以不等第一个请求响应继续发送后面的请求,但响应的顺序还是按照请求的顺序返回
- 缓存处理:新增字段cache-control
- 断点传输:在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载
HTTP/2
- 二进制分帧传输:更方便头部,只传输差异部分
- 多路复用:同一服务下只需要用一个连接,节省了连接
- 头部压缩:合并同时发出请求的相同部分
- 服务器推送:一次客户端请求服务端可以多次响应
HTTP/3
基于谷歌的QUIC,底层使用udp代码tcp协议,解决了队头阻塞问题,同样无需握手,性能大大地提升,默认使用tls加密。
进程和线程
进程拥有的资源
- 进程控制块
- 文件描述符
- 网络连接
- 设备
区别:
- 进程是系统资源分配的最小单位,实现了操作系统的并发;线程是CPU调度的最小单位,实现了进程内部的并发。
- 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
- 进程切换的开销远大于线程切换的开销。
- 进程之间不会相互影响,但线程挂掉会影响整个进程。
进程间通信方式:
- 管道
- 信号量
- 消息队列
- 共享内存
- 套接字,也可以用于不同主机之间进程的通信
线程间通信方式:
- 共享内存,比如 volatile 保证内存的可见性。
- 消息传递,比如 wait/notify/notifyAll 等待通知方式和 join 方式。
- 管道流
用户态和内核态
为什么要有这两个状态?保护机制,防止用户误操作或恶意破坏系统
用户态
- 是用户进程/线程所在的区域,主要用于执行用户程序
- 运行的代码会受到CPU的很多检查,不能直接访问内核数据
- 只拥有受限的权限
- 只能响应部分中断请求
- 只能访问受限的地址空间
内核态
- 是内核进程/线程所在的区域,主要用于执行操作系统程序,硬件交互
- 运行的代码不受任何限制,可以执行任意指令
- 拥有最高权限
- 可以响应所有中断请求
- 可以访问所有内存空间
用户态切换到内核态
- 系统调用(主动)。操作系统在执行用户程序的时候主要工作在用户态,当执行没有权限的任务时,才切换到内核态。
- 异常(被动)。执行用户程序出现异常时,会从用户态切换到内核态处理异常
- 外围设备中断(被动)。中断发生时,如果中断之前在运行用户态的程序,那么会切换至内核态处理中断。
服务注册与发现
如果自己实现服务注册与发现,需要考虑以下三点:
- Register, 服务启动时候进行注册
- Query, 查询已注册服务信息
- Healthy Check, 确认服务状态是否健康
实现方案:
- Eureka,AP
- ZooKeeper,CP,Paxos 算法
- Consul,CP,Raft 算法
- Etcd,CP,Raft 算法
Paxos 和 Raft 算法都属于一致性算法,所以是保证 CP
实际上,作为一个注册中心来说,保证 AP 更加重要,即可用性。
两种模式
- 客户端发现模式,首先要进行的是到服务注册中心获取服务列表,然后再根据调用端本地的负载均衡策略,进行服务调用。
- 服务端发现模式,调用方直接向服务注册中心进行请求,服务注册中心再通过自身负载均衡策略,对微服务进行调用。这个模式下,调用方不需要在自身节点维护服务发现逻辑以及服务注册信息。
常见的负载均衡
- 轮询,按照请求的顺序轮流分配到不同的服务器,循环往复。
- 加权轮询,给不同的服务器分配不同的权重,根据权重比例来决定分配请求的数量。
- 最小连接数
- 最短响应时间
- IP哈希
RPC/HTTP
与 HTTP 的对比,RPC 使用二进制传输,传输效率高(HTTP额外空间开销大,包含大量元数据,头字段等),但通用性不如 HTTP 协议
核心
- 消息协议:以何种方式打包编码和拆包解码
- 传输控制:主要有HTTP传输和TCP传输,鉴于TCP传输的可靠性,RPC的传输一般使用TCP作为传输协议
工作流程
- 客户端(Client)以本地方法调用服务
- 客户端存根(Client Stub)收到调用后把方法、参数等内容打包成特定格式、能进行网络传输的消息体(Marshalling)
- 客户端存根找到服务地址,发送给服务端(这个过程可以基于TCP也可以基于HTTP)
- 服务端存根(Server Stub)收到消息后拆包解码(Unmarshalling)
- 服务端存根根据方法名和参数进行本地调用服务
- 服务端(Server)本地执行后把结果返回给服务端存根
- 服务端存根把结果打包发送给客户端存根
- 客户端存根接收到消息进行解码
- 客户端得到最终结果
实现方式
- 基于 http
- 基于 tcp(常见)
计算机网络
TCP/UDP 可以使用同一个端口吗
可以。传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块。可以在 IP 包头的协议号字段判断出 TCP 还是 UDP
TCP 三次握手
为什么三次握手
因为三次握手才能保证双方具有接收和发送的能力,并且防止重复建立历史连接。
为什么四次挥手
本质原因是 TCP 是全双工通信,客户端确认没有数据发送后,发出结束报文,此时服务端返回确认后,服务端也不会接收客户端数据。但是此时服务端可能还有数据没有传输完,客户端还是可以接收数据。
如果挥手过程中「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
DNS使用TCP还是UDP
DNS 在进行区域传输的时候使用 TCP,其他情况使用 UDP。
区域传输:是指DNS主从服务器之间的数据同步,保证数据的一致性,传送会利用DNS域,所以就称为DNS区域传送。
TCP 保证可靠传输
- 序列号
- 连接建立:三次握手四次挥手
- 头部检验和
- 确认应答
- 超时重传
- 拥塞控制:四种算法
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
- 流量控制:滑动窗口实现
OSI 七层模型和 TCP/IP 四层模型
- 应用层:HTTP,DNS,FTP,WebSocket;网关
- 应用层
- 表示层
- 会话层
- 传输层:TCP,UDP;网关
- 网络层:IP(在TCP/IP模型中,ARP属于网络层);路由器
- 网络接口层
- 数据链路层:ARP;网桥,交换机
- 物理层:网卡;中继器,集线器
架构
在高并发环境下,服务之间的依赖关系导致调用失败,解决的方式通常是: 限流->熔断->隔离->降级, 其目的是防止雪崩效应。
死锁
四个条件:
- 互斥
- 请求和保持
- 不可剥夺
- 循环等待
一致性哈希
一致性哈希是指将「存储节点」和「数据」都映射到一个首尾相连的哈希环(固定大小)上。数据存储在哈希值通过顺时针找到的第一个节点。
优势
一致性哈希算法是对 2^32
取模,是一个固定的值;而普通哈希表的长度是由节点数决定的。
- 普通哈希函数,如果节点数量发生了变化(对系统进行扩容缩容的时候),大部分改变了映射关系,因此需要迁移大量的数据。
- 一致性哈希算法,如果节点数量发生了变化,只影响该节点顺时针相邻的后继节点。
虚拟节点的引入
一致性哈希算法并不保证节点能够在哈希环上分布均匀,因此引入均匀分布的虚拟节点,建立真实节点和虚拟节点的映射关系。
IO
- 同步
- 同步阻塞IO
- 同步非阻塞IO
- IO多路复用
- 信号驱动IO
- 异步
- 异步IO
虚拟内存
虚拟内存是一种计算机技术,它允许系统将一部分硬盘空间当作RAM(随机存取存储器)使用。当物理内存不足以支持正在运行的应用程序时,系统会将不常用的数据移动至磁盘中。虚拟内存为每个进程提供了一个一致的、私有的地址空间
32位/64位操作系统的区别
32位/64位表示CPU可以处理最大位数,一次性的运算量不一样,寻址能力也不同。
域名解析流程
- 检查缓存
- 浏览器缓存
- 操作系统缓存
- 本地 hosts 文件
- 使用递归查询向本地域名服务器查询
- 使用迭代查询向跟服务器查询
- 根域名服务器(.)
- 顶级域名服务器(.com)
- 二级域名服务器(google.com)
排查 CPU 占用过高和内存溢出的问题
Java
CPU 占用排查:使用 top
和 top -Hp xxx
命令定位占用率最高的进程和该进程的线程 内存占用排查:jstack
, jmap
打印出堆栈信息, jstat
查看垃圾回收的情况
- Jstat 可以查看新生代的两个S0、s1区、Eden区,以及老年代的内存使用率,还有young gc以及full gc的次数。
- Jmap 可以查看当前堆中所有每个类的实例数量和内存占用,也可以 dump 内存快照,再由 VituralVM 或 MAT 软件可视化查看。
Go
使用 pprof 工具,可以查看 CPU 占用、排查内存泄漏、协程泄漏等。
互斥锁和自旋锁
- 互斥锁是一种阻塞锁,获取不到锁的时候,线程会被挂起。
- 自旋锁是一种非阻塞锁,获取不到锁的时候,线程不会被挂起,而且不断去获取,消耗 CPU 资源。
缓存 IO,直接 IO,裸 IO
- 缓存IO:读数据时先从内核空间的缓冲区读,如果没有则从磁盘中读并缓存到缓冲区;写数据将用户空间的数据复制到内核空间的缓冲区,并标记为脏页,操作系统后台将脏页写入磁盘中,一般用于频繁读写的小文件;
- 直接IO:直接读写文件,而不经过内核缓冲区,目的是减少一次内核缓冲区到用户程序缓存的数据复制,一般用于不需要频繁读写的大文件;
- 裸IO:绕过文件系统,直接读写磁盘块设备数据,一般用于数据库;
Base64 和 Base62 的区别
编码
- Base64:26 个大写字母 + 26 个小写字母 + 10 个数字 +
+
+/
;并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的「/」和「+」字符变为形如「%XX」的形式。 - Base62:26 个大写字母 + 26 个小写字母 + 10 个数字。
实现
- Base64:将输入字符串按字节切分,取得每个字节对应的二进制值(若不足 8 比特则高位补 0),然后将这些二进制数值串联起来,再按照 6 比特一组进行切分(因为 2^6=64),最后一组若不足 6 比特则末尾补 0。若原字节序列数据长度不是 3 的倍数时且剩下 1 个输入数据,则在编码结果后加 2 个 =;若剩下 2 个输入数据,则在编码结果后加 1 个 =。将每组二进制值转换成十进制,然后找到对应的符号并串联起来就是 Base64 编码结果。
- Base62:将输入字符串哈希后转成长整型,再用62进制编码成Base62格式。