互联网应用服务端的常用技术思想与机制纲要

拓展技术边界,提升业务创新能力。


不患不知,而患不精。精益求精,技术过硬是必要的。理解互联网技术的底层原理与实现,对提升设计能力非常有益处,亦能为业务创新提供重要的技术保障。技术深度不够会限制业务创新能力。

本文旨在聚合互联网应用的服务端所用到的主要且重要的技术思想、原理、机制、技巧。

综述

软件的使命是处理数据。人赋予数据以意义。

数据处理的要求:灵活、高性能、高可靠、高可用、一致、海量、安全、自动化、智能化。

  • 灵活:数据建模与存储、架构与编程、微服务、框架、代理、组件与配置化。
  • 高性能:数据结构与算法、并发、缓存、精简开销、参数调优、编译优化、服务器性能、服务指标测量。
  • 高可用:限流、降级、冗余。架构上要支持水平扩展,运维上多节点部署、负载均衡、弹性伸缩容(容器化)。
  • 高可靠:分布式 + 容错。
  • 一致: 复制、状态机、一致协议、重试补偿对账。
  • 海量:分布式 + 大数据存储 + 实时计算 + 离线分析
  • 安全:机密性、完整性、可用性。机密性 - 数据内容不泄露;完整性 - 数据内容不被篡改;可用性 - 保护资源随需所得。
  • 智能化:让机器从大量已有数据中自动学习和分析模式,增加反馈环节,来改善产品、业务的前置环节。

视角

解决问题视角

  • 解决什么问题,有哪些情形,要考虑什么错误和异常 ?What - Problem - Circumstances - Errors - Exceptions
  • 为什么要解决这个问题 ? 造成了什么麻烦,有哪些应用场景 ? Why - Troublesome - Applications
  • 如何解决:理念、方法、实践如何 ? How - Thoughts - Methods - Practices
  • 方案对比:优点和缺点,适用场合 ? Evaluation - Benefits - Drawbacks

系统设计视角

  • 核心抽象、概念及类。 Abstraction - Conception
  • 主流程、重要环节、核心类的串联。 Flow - Segments - Links
  • 问题分解、技术难点与方案、可行性论证。Decomposition - Keypoints - Solutions - Feasibility
  • 关键数据集及算法。 Core Dateset - Algorithm

技术决策视角

  • 场景考量:通用场景,还是特定场景? 场景是否有特殊性,可以用更高效的方式有针对性地解决?场景下的语境上下文是怎样的 ? Scene - Context
  • 方案权衡:要达成的目标是怎样的? 哪些质量因素是尤为重要的?要兼顾考虑哪些质量因素?可用资源如何? Goal - Quality Requirements - Available Resources

思想

纵观各种技术:基本思想是在不同语境里反复使用着的。

  • 功能与灵活: 标识、标记/状态位、元数据;分离与解耦。
  • 时间复杂度: 二分、映射、分治与组合、分摊、缓存、缓冲;动态规划;预处理;空间换时间。
  • 空间复杂度: 位图、冗余编码、虚拟化;时间换空间。
  • 高效读:缓存优先于主存,主存优先于磁盘,缓冲优先于直接读写;减少磁盘读次数,减少更慢存储介质的读次数。
  • 可靠写:主存优先于缓存、磁盘优先于主存;提交/确认/回滚机制。
  • 提升响应:批量、异步、并发。
  • 可维护性:抽象(问题本质)、封装(实现隐藏)、复用(提升效率)、多态(增强灵活性)、组合(功能集成)。

可扩展性

“【整理】系统可扩展性的设计与实现 ”

高性能

提升性能,即是用更少的资源做更多的事情。资源类型包括:CPU 时间片、内存空间、磁盘空间、网络带宽、连接池、缓存等。弄清楚应用程序依赖哪些资源类型以及依赖程度如何。

性能通常用 RT 和 吞吐量来衡量。 RT 是单个请求的处理时间, 吞吐量是指定时间内处理的请求数。应用也分为响应敏感型和吞吐量敏感型。比如订单详情,就是响应敏感型,RT 过高会导致超时,表现为服务不稳定; 而订单导出,则是吞吐量敏感型,单个请求处理长一点不影响体验,单位时间的吞吐量越高越好。

数据结构和算法的设计是性能提升的基本层面。并发是通过多个 Woker 并发或并行处理的思路。缓存是不同读写速度的存储介质及淘汰算法的权衡。精简开销和参数调优更多是针对热点和耗时进行细节上的调优。编译优化是语言层面的优化。高配服务器能直接提升性能,但成本较高。最后,性能测量必不可少。只有理论值是不够的。

高性能的核心:更少的 CPU 周期(数据结构、算法、内存计算),更少的等待时间(IO 读写、加锁与解锁、内核与用户态的切换)。

常见硬件性能:L1 cache (0.5ns) > 分支预测失败(5ns) OR L2 cache (7ns) > Mutex 加锁与解锁 (25ns) > 内存访问 (100ns) > 固态盘 SSD 访问延迟(0.1ms) > 机房内网络来回 (0.5ms) > 千兆网络发送 1MB (10ms) OR SATA 磁盘寻道 (10ms) OR SATA 顺序读取 1MB 数据 (20ms) > 异地机房网络来回 (30-100ms) 【来源:《大规模分布式存储系统》2.1.4 】

数据结构与算法

“【整理】盘点程序员必知必会的常见数据结构和算法”


分区

“【整理】互联网服务端技术体系:可扩展之数据分区”

并发

“【整理】互联网服务端技术体系:高性能之并发”

缓存

“【整理】互联网服务端技术体系:高性能之缓存面面观”

精简开销

  • 热点分析,定位开销大的地方。比如订单导出的热点耗时区域在批量获取订单的详情内容上。
  • 移除不必要:去掉不必要的访问、去掉重复开销。比如获取订单详情时不需要的字段就不去访问相应的 API。
  • 精简链路:去掉重复调用,合并调用,调用结果缓存与传递。比如交易可以拿到商品信息并缓存、传给营销中心。
  • 无锁化:CAS 机制。去掉加锁和释放锁的操作耗时,增加了 CPU 轮询开销(两害权衡取其轻)。
  • 非阻塞IO:Select 和 Epoll 机制。减少不必要的线程切换和数据拷贝。

非阻塞IO

  • 阻塞IO:需要为每个 Socket 建立线程(线程数量有限),创建大量线程(空间开销大),线程容易被IO阻塞等待(利用率低),大量的线程切换开销(时间耗费在与业务无关的事情上)。
  • 非阻塞IO:IO 多路复用思想;Select机制、Epoll 机制。
  • Select 机制 : Buffer - Channel - Selector 。 一个线程可以管理多个 selector ,每个 selector 可以轮询监听多个 Channel 的事件并读取或写入数据,每个 channel 与一个 Buffer 相连,提升 IO 读写的效率。select 相对于阻塞IO有进步,但仍然有缺点:1. 每次 select 都需要将 fd 集合从用户态拷贝到内核态,并在内核态遍历所有的 fd ;fd 越多开销越大; 2. 一个进程支持的 fd 集合大小受限,默认是 1024 ; 3. 如果没有 fd 处于就绪状态,select 会阻塞。
  • Epoll 机制:三个函数 --- epoll_create ,epoll_ctl,epoll_wait 。epoll_create 会创建一个 epoll 实例, 其中包含一棵红黑树 rbr 存储需要监控的 fd 集合,双链表 rdlist 存储返回给用户的满足条件的事件; epoll_ctl 向 epoll 实例注册给 fd 要监听的事件;epoll_wait 等待内核返回监听 fd 的事件发生,返回已就绪的事件及数量。Epoll 有 LT 和 ET 两种工作模式。LT --- 检测到 fd 的事件就绪通知应用程序后,可以暂时不处理,事件放回就绪链表中,待下次调用 epoll_wait 时再通知;ET --- 必须立即处理。 Epoll 的优点: fd是共享在用户态和内核态之间的(mmap技术),不需要将 fd 在内核态和用户态之间拷贝,不需要遍历就可以获得就绪的 IO 事件。一个进程支持的 fd 集合大小只受操作系统限制。
  • Netty: ByteBuf - Channel - ChannelEvent - EventLoop - ChannelPipeline - Channel Handler - Callback - Channel Handler Context - ChannelFuture 。Channel 是数据流 ByteBuf 的出入通道,网络连接和数据事件发生的起点。当 Channel 的某个事件 ChannelEvent 发生时,EventLoop 会轮询 ChannelEvent 已经注册的 Channel Handler ,并执行 Channel Handler 对应的回调函数 Callback。多个 Channel Handler 可以构成一个 Channel Pipeline。 Future 提供了异步返回结果和通知的方式。Future 可以注册 FutureListener ,从而在 Future 执行完成时回调 FutureListener 的方法 operationComplete。一个 Channel 在其整个生命周期里只关联一个 EventLoop 且它的所有 ChannelEvent 都由这个 EventLoop 处理。EventLoop 是 netty 的事件并发执行模型。
  • Redis 多路复用: evport, epoll , kqueue , select

HTTP2优化

  • HPack 压缩首部算法,常用首部做成静态表映射获取, 哈夫曼编码。
  • 借鉴 TCP ,数据划分为更小的二进制帧进行传输;
  • 多路复用,单连接多流并行;
  • 服务器主动推送相关资源,而非浏览器发送多次请求,减少 TCP 连接耗时;
  • 应用层重置连接和请求优先级;
  • 流量控制。

参数调优

  • JVM 调优
  • 应用配置参数调优。首先提取影响系统功能、性能、稳定性、可用性等的重要因子,然后通过配置平台来管理。参数配置可以包括策略选择、超时设置、重试次数、批次处理数、限流因子等。比如订单导出的策略选择有报表维度(商品/订单/商品订单均要)、文件上传下载维度(本地服务器或云存储); 订单导出并发批量拉取订单详情,可以配置分批处理的订单数,并发拉取的订单数,每批次订单处理的时间间隔、超时重试次数等。

服务指标测量

在工程上,测量是尤为重要的。理论只是给出了定性值,而测量将给出更精确的量化值。

  • 基本指标: 单机 RT 和 QPS。运行多次单个请求,取请求响应时间的平均、峰值、最小值、百分位数。平均值即为 RT 值。QPS = 1 / RT(ms) 。
  • 并发指标: 并发数。通过压测来测试并发负载能力。

高可靠

分布式

分布式通过异构、冗余实现容错和扩展能力。

  • CAP:一致性、可用性、分区容错。 一致性要求不高的,通常采用 AP; 资金敏感的,通常采用 CP。
  • BASE: 基本可用,软状态、最终一致性。牺牲强一致性来获得可用性。
  • 协议:Paxos,Raft

选举算法

选举算法是分布式系统的基础。许多分布式都是基于 Master-Slaves 或 Leader - Followers 模式。一般 Leader 负责写数据,而 Followers 负责同步写入的数据到自己的副本上保持与主一致。

  • 选举算法:Bully 算法、FastLeader 算法(ZK)。Bully -- ID 最大的作为 Leader ,有频繁换主的风险。

选举机制的组成部分:

  • 选举轮次标识:时间戳或编号,防止过期选举消息干扰;
  • 选举检查项:比如服务器状态;
  • 选举依据:根据什么来比较,判断谁应当成为 Leader ;
  • 投票统计:每个服务器都投出自己的一票,并接收别人的投票做某种处理,和自己的投票来做统计;
  • 选出 Leader : 根据统计结果,大部分服务器都能达到共识,Leader 从共识中产出。

ZK

  • 实现分布式协同服务。CP 模型、ZAB 协议、ZNode、树形结构、Watch 通知。 ZK 只能存少量元数据,不适合做分布式存储系统。
  • ZK 的数据结构:节点以树的形式组织起来。节点分 持久/ 临时节点,有序/无序节点。
  • ZK 服务器数最好为奇数个:1. 偶数个只允许更少服务器崩溃,更脆弱; 2. 防止脑裂现象。
  • ZK 以事务的方式执行所有操作,并确保所有事务以原子方式执行,不受其它事务的干扰。ZK 事务具有原子性和幂等性。zxId 作为事务的标识。zxId 是 64 位的值,分为 32 位时间戳和32位的计数器。32位时间戳可以用于识别当前时段的 Leader 和事务时间戳。
  • 选举算法: 每个服务拥有一个 Eo (sId, zxId) ,并向其它服务器发送自己的 Eo (sId, zxId) 。 收到 Er 的每个服务器将 Er 与 Eo 进行比较。如果 Er.zxId > Eo.zxId 或者 Er.zxId = Eo.zxId AND Er.sId > Eo.sId ,则用 Er 替换 Eo 。最终大部分服务器都会拥有相同的 Er。从而选举出具有 Er 的那个服务作为 Leader 。
  • ZAB:S1- Leader 向所有 Follower 发送一个事务的 Proposal 消息 P; S2- 每个 Follower 收到消息 P 后响应一个 ACK ,表示同意该事务; S3- 当仲裁数量的服务器发送 ACK 消息后,Leader 将通知 Follower 进行提交事务。

容错

“【整理】互联网服务端技术体系:可靠性与容错”


高可用

可用性策略

  • 可用性评估:平均失效时间(MTBF)和平均恢复时间(MTTR)。不可用时间 -- 99%-5256m,99.9%-526m,99.99%-53m, 99.999%-5m。获得可用性与投入成本是非线性的。越高的可用性需要越高的成本,因此评估应用所需的可用性是必要的。提炼应用的核心模块,对更小的系统提升可用性成本会更小。使用风险敞口来评估优先考虑的可用性需求。风险敞口 = 失效概率 * 失效损失。
  • 宕机原因分析: 1. 运行环境,磁盘故障居多; 2. 性能问题,大流量杀手;3. 软件 bug; 4. 变更管理操作出错。
  • 高可用策略:思路 - 避免导致宕机的主要原因、快速恢复故障。三大措施 - 限流、降级、冗余。方法层面 - 避免单点失效、冗余和故障转移。执行层面 - 主备、对称布局;评估组件切换时间和故障恢复时长(估算和演练);备库演练(记录切换时的工作负载);虚拟IP、代理、端口转发、NAT;运维操作安全准则、故障演练。

冗余

冗余是保证可用性的基础。

  • 冗余是指一份数据有多个副本存储在不同的节点上。冗余是应对容错和提升可用性的重要机制,同时又会带来一致性问题。
  • 副本协议:中心化副本协议,primary-backup 协议、去中心化副本协议。主节点写入成功后,发送操作日志给指定节点或全部节点的副本,指定节点或全部节点根据操作日志写入成功后回复主节点,主节点回复写入成功。
  • 一致与可用:强一致复制 -- 如果所有副本写入成功,主节点才返回客户端成功,则是强一致同步复制。但如果某个副本失败,则整体写入失败,此时无法达成可用性。弱一致复制 -- 只要主副本写入成功,就返回客户端成功,通过线程异步发送给其他节点去更新副本,则是弱一致同步复制。某个副本写入失败,不影响整体写入的结果。此时,可以达成可用性,但无法达成强一致性。
  • 多个副本不能全部放在同一个节点或机架上。否则,节点或机架故障时,数据会丢失,无法达成可用性。

复制

“【整理】互联网服务端技术体系:高可用之复制技术”


限流

熔断降级


一致性

数据的多副本在一定时刻点之后保持一致性的要求。

分类

要保证数据一致性,根据应用实际需求,有多种一致性的定义。

  • 时间点一致性:如果所有相关的数据组件在任意时刻都是一致的,那么可以称作为时间点一致性。
  • 事务一致性:事务的一致性指的是数据库的数据从事务执行之前的有效状态变更为事务执行之后的另一种有效状态。数据有效状态是指必须满足数据的约束与完整性(事先在数据库里定义的数据规则集合)。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于新的有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始有效状态。
  • 应用一致性:在应用程序中涉及多个不同的单机事务,在所有的单机事务完成之前和完成之后,多个应用的数据总是符合指定的规则集合。
  • 区别与联系:事务一致性和应用一致性,可以看成数据约束和完整的有效性,这些数据不一定是同一个数据项的定义(比如支付成功后送优惠与送积分,退款后退券与退积分);时间点的一致性,主要是多副本的数据内容要相同,通常属于同一个数据项定义(比如DB、ES、HBase 上的订单状态是同一个值)。

程度

一致性程度的观察角度:从所有的复制节点来看。

  • 一致性程度: 线性一致性 > 顺序一致性 > 因果一致性 > 最终一致性
  • 线性一致性:亦称原子一致性。任何进程的写操作,如果一个节点进程的读操作读到了最新值,则所有后续的读操作都应当读到最新值。反之,则不满足线性一致性。难点:分布式下的统一的全局时钟。
  • 顺序一致性:顺序一致性是指所有的进程以相同的顺序看到所有的修改。读操作未必能及时得到此前其他进程对同一数据的写更新。但是每个进程读到的该数据的不同值的顺序是一致的。
  • 因果一致性: 有因果关系的操作满足顺序一致性。比如一个线程先写一个变量,再读一个变量,那么它读到的一定是写之后的值。就满足因果一致性。
  • 最终一致性:不需要关注中间变化的顺序,只需要保证在某个时间点一致即可。

手段

  • 协议算法
  • 异步复制
  • 实时计算与同步
  • 重试、补偿

Raft协议

  • Raft 是一个基于复制状态机的一致性算法,用于分布式存储系统。Raft 将一致性保证分解如下子问题:Leader election, Log replication, safety, and membership changes。Raft 保证一个强有力的 Leader ,只允许 Leader 发送复制日志 ;复制状态机通常使用复制日志实现,每个服务器存储一个包含一系列命令的日志,其状态机按顺序执行日志中的命令。 每个日志中命令都相同并且顺序也一样,因此每个状态机处理相同的命令序列。 这样就能得到相同的状态和相同的输出序列;Safety 机制用来保证在 Leader, Follower 崩溃的情况下,依然能够保证 Leader 总有所有已提交的日志。一致性算法的工作就是保证复制日志的一致性。
  • Leader election:时间被随机划分为多个时间片。每个时间片作为一个“任期”,在任期内进行选举,选举出来的 Leader 负责管理 log replication。Leader 周期性地向所有 follower 发送心跳(不包含日志条目的 AppendEntries RPC)来维持自己的地位。每个服务器都有一个任期号。如果 RequestVote RPC 中的任期号比自己小,那么 candidate 就会拒绝这次的 RPC 并且继续保持 candidate 状态。Raft 算法使用随机选举超时时间的方法来确保很少发生选票瓜分的情况,就算发生也能很快地解决。
  • Log replication:Leader 一旦被选举出来,就开始为客户端请求提供服务。客户端的每一个请求都包含一条将被复制状态机执行的指令。Leader 把该指令作为一个新的条目追加到日志中去,然后并行的发起 AppendEntries RPC 给其他的服务器,让它们复制该条目。当该条目被安全地复制,leader 会应用该条目到它的状态机中(状态机执行该指令)然后把执行的结果返回给客户端。如果 follower 崩溃或者运行缓慢,或者网络丢包,leader 会不断地重试 AppendEntries RPC(即使已经回复了客户端)直到所有的 follower 最终都存储了所有的日志条目。复制状态机用于解决分布式系统中的各种容错问题。
  • Safety:Raft 使用投票的方式来阻止 candidate 赢得选举除非该 candidate 包含了所有已经提交的日志条目。候选人为了赢得选举必须与集群中的过半节点通信,这意味着至少其中一个服务器节点包含了所有已提交的日志条目。如果 candidate 的日志至少和过半的服务器节点一样新(接下来会精确地定义“新”),那么他一定包含了所有已经提交的日志条目。RequestVote RPC 执行了这样的限制: RPC 中包含了 candidate 的日志信息,如果投票者自己的日志比 candidate 的还新,它会拒绝掉该投票请求。Raft 通过比较两份日志中最后一条日志条目的索引值和任期号来定义谁的日志比较新。如果两份日志最后条目的任期号不同,那么任期号大的日志更新。如果两份日志最后条目的任期号相同,那么日志较长的那个更新。Safety 五法则:1. 至多只有一个 Leader 被选举; 2. Leader 只能添加新日志条目,不能修改或删除日志条目; 3. 两条日志条目具有相同的 index 和 任期,则视为等同; 4. 如果一个日志条目被提交,那么它一定出现在最高任期的 Leader 的提交日志条目里; 5. 如果一个节点服务器应用了某个 index 在状态机上,那么没有任何一个其他节点服务器会在相同的 index 具有不同的日志条目。

海量

存储

数据存储及索引结构

事务

文件系统

  • 音视频存储

分析

  • Hive: Hive SQL

实时计算

  • Storm, Spark , Flink

安全

  • 常见安全问题:缓冲区溢出、XSS、SQL注入、DDOS
  • 安全评估:资产(数据)等级分析、威胁分析、风险分析、解决方案确认
  • 机密性保证:加密(对称加密和非对称加密)、脱敏(敏感信息避免模糊化处理)、代理隐藏、权限管控
  • 完整性保证:数字签名
  • 可用性保证:黑名单、限流、防火墙、清洗流量
  • 威胁建模:STRIDE 模型。伪装、篡改、抵赖、信息泄露、拒绝服务、提升权限。
  • 风险分析:DREAD 模型。Damage Potential(完整验证权限、执行管理员操作、非法上传文件)、Reproducibility (随意再次攻击)、Exploitability(易学性,初学者可以很快学会)、Affected Users(所有用户、默认配置、关键用户)、Discoverability(漏洞很显眼,攻击条件易得)
  • Secure by Default: 白名单原则 , 最小权限原则。 采用黑名单容易绕过或遗漏。白名单要避免范围过大的通配符。
  • 纵深防御原则:从不同层次、不同方面实施安全方案,构成防御整体。避免薄弱环节与短板。
  • 数据与代码分离原则:防止 缓冲区溢出、SQL 注入、XSS。
  • 不可预测原则:让系统的关键运行区域显示出随机性,从而大幅提升恶意构造请求来攻击的难度和成本。请求 token 机制。
  • 源头堵截原则:在源头上堵截漏洞,而不要依靠很多人通过学习来掌握防御技术。让防御变得易学易用。

智能化

  • 机器学习

平台

操作系统

内存管理

  • 虚拟内存。建立从虚拟地址空间到物理内存地址的映射。由 MMU 中的地址翻译硬件、页表(PT,常驻物理内存,进程专享)、操作系统共同实现。虚拟内存的用途:1. 磁盘空间的缓存工具; 2. 内存管理工具,为进程提供独立的地址空间,互不影响; 3. 灵活的内存分配,虚拟地址可以连续,而对应的物理地址可以不连续;4. 保护内存不受进程破坏,不允许修改只读代码段和内核的代码与数据,不允许修改其他进程的地址空间的数据,不允许修改共享页面。可以为 PT 添加一些许可位来控制进程的读写权限。
  • 内存布局。指各种元数据、数据、代码段在内存中的有序布置。设计良好的内存布局,更有利于访问数据和代码。具体示例可参考 ELF 可重定向目标文件格式。JVM 也有自己的内存布局。
  • 内存分配。内存分配策略 - 首次适配(简单)、最佳适配(碎片小,但很难再次分配,需要合并)、最坏适配(分配后仍然可再次分配,大块内存难以满足)。算法 - Buddy, CMA, slab。Bump the Pointer(如果未使用内存与已使用内存分离了,Serial, ParNew)、Free List(找适配大小的空闲块,Mark-Sweep, CMS)。分配内存的并发问题解决方案:CAS 机制和 预先分配的 TLAB(本地线程缓冲分配)。指向对象的指针或引用在虚拟机栈上分配。

程序执行

  • 链接与加载。将各种代码片段、数据片段进行收集并组合起来,构成完整的可执行目标文件,并加载到内存中执行的过程。链接与加载涉及模块和库依赖、符号引用、作用域、动态共享库之类的话题。链接器的主要任务:符号引用和重定向。每个可重定向目标模块都有一个符号表,用来做符号解析。

JVM

内存管理

  • 内存布局:PC寄存器(指令地址)、虚拟机栈(方法执行)、本地方法栈(Native方法执行)、堆(对象内存分配)、方法区(类加载信息、运行时常量池、静态变量、即时编译信息等)、直接内存(NewIO)。每个线程都有专属的【虚拟机栈、PC寄存器 、本地方法栈】。
  • 对象创建和初始化:new 参数 -> 定位类引用 -> 检查类是否加载、解析、初始化 -> 如果没有则执行类加载过程 -> 为对象分配内存 -> 分配空间清零 -> 对象头内容设置 -> 执行 _init 方法初始化对象。
  • 对象内存布局:对象头(MarkWord)、实例数据、对齐填充。 对象头 -- 对象哈希码、分代年龄、轻量锁、重量锁、偏向、类元数据指针。实例数据 -- 虚拟机字段分配策略和程序指定顺序。字节数相同的类型的字段放在一起分配。对齐填充 -- 对象起始地址必须是 8 的整数倍。访问对象的方式:对象句柄池和直接指针。使用对象句柄的好处是引用的地址是稳定的,只要改变句柄的值(对象移动时),使用直接指针的好处是效率更高,减少一次指向过程。
  • 对象引用分析:对象可达性遍历判断对象是否活跃。强、软、弱、虚引用。强 - 只要有引用就不回收;软 - 在抛出 OOM 之前进行回收;弱 - 下一次 GC 时回收;虚 - 持有虚引用的对象在GC时可以收到系统通知。
  • GC算法:复制算法(新生代,优化:迭代避免溢出、多空间提升空间利用率);标记-清除算法(老年代,优化:多空闲链表、位图标记、延迟清除)。 GC 收集器:三个维度,单线程与多线程,Client 与 Server,新生代和老年代。Serial- 单线程,Client, Par- 多线程。CMS 更好的响应,Parallel Scavenge 更好的吞吐。G1 :可预测停顿时间。 GC 日志分析。
  • 内存问题:OOM, Memory Leak , Memory Overflow,Stack Overflow ,Method Area Overflow。 OOM -- 线程转储,定位原因;Memory Overflow -- 堆内存空间不足,死循环; Memory Leak 通常是资源没有正确释放,无法及时 GC ,使用 MAT 等工具分析从 GC ROOT 的引用链;Stack Overflow -- 递归过深,分配数组大小过大;Method Area Overflow -- 字节码技术动态生成或增强的大量类载入方法区可能导致方法区溢出(CGLib, JSP, OSGi)。
  • FullGC 原因及方案:1. 大对象列表,很大的列表,拆分小对象/小列表,及时释放; 2. Old 空间小,可以调大 Old 区; 3. 不要轻易手动调用 System.gc 方法。

类加载

  • 平台无关性和语言无关性:字节码与虚拟机。将高级语言编译成符合虚拟机规范的字节码,从而能够在符合规范的虚拟机上执行字节码,最终映射到本地机器指令集。操作系统平台和编程语言对应用程序是透明的。
  • 重要元素:Class 类文件 - 字节码指令集 - 类加载器 - 加载机制。Class 类文件是 Java 类或接口的字节码表示,字节码指令集是字节码的主要元素。类加载器根据 Class 类文件在虚拟机中创建类对应的 Class 对象 。Java 类加载器包含 Bootstrap 加载器、Ext 加载器、App 加载器、Customized 加载器。类加载的顺序是先用前面的加载器进行加载,找不到则用下一个。类或接口的唯一标识:类或接口的名称 + 定义类加载器。不同类加载器加载同一个类或接口在 JVM 是不同的。
  • 类加载过程:加载-链接-与初始化:加载 -- 查找类或接口的二进制表示,并据此来创建类或接口的 Class 对象; 链接 -- 将类或接口链接到虚拟机运行时状态,链接包括验证、准备、解析; 初始化 -- 执行初始化方法
  • 加载:运行时常量池存储着类、类的成员和方法的各种信息的二进制表示,符号引用来自于 CONSTANT_XXX_info 结构。
  • 初始化:调用 new, getstatic, putstatic, invokestatic 指令时触发。初始化需要保证多线程环境的安全性,由虚拟机负责实现。每个类或接口 C 都有一个对应的唯一初始化锁 LC。初始化之前需要先获取这个锁 LC ,分情况进行处理并释放锁。

安全机制

  • 安全模型:沙箱模型、限定于 JVM 范围。主要是对本地系统资源的访问限制。类加载验证。安全策略。域、代理、权限组。doPrivileged。

小结

数据结构与算法是构建互联网的基本材料,网络、存储、操作系统、分布式是现代互联网应用的服务端的基石,而语言、平台、框架、中间件则是在基石之上的基础设施,它们共同构建了稳固的互联网大厦。

体系结构是一种强大的方法论。打造一个知识体系,构建底层逻辑,理解上层变化,自下而上打通,持续融入所学。

PS:以前,我常觉得业界写具体技术的书比较多,而总结软件设计思想和案例的书太少,不利于开发者的设计能力的快速提升。现在看来,业界处处都有优秀的框架、中间件、平台设计,简直是宝山在眼前,而我却视若无睹。不过,要能从这宝山里挖掘财富,其实也不容易。

参考文献

posted @ 2020-09-08 16:33  琴水玉  阅读(2876)  评论(3编辑  收藏  举报