Java知识点补充(一)
1.操作系统
1.1用户态和内核态是如何切换的?
用户态切换到内核态的 3 种方式:
- 系统调用(Trap):用户态进程 主动 要求切换到内核态的一种方式,主要是为了使用内核态才能做的事情比如读取磁盘资源。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
- 中断(Interrupt):当外围设备完成用户请求的操作后,会向 CPU 发出相应的
中断信号
,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。 - 异常(Exception):当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
在系统的处理上,中断和异常类似,都是通过中断向量表
来找到相应的中断处理程序
进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
2.分布式限流
2.1基于中心化的限流方案
通过一个中心化的限流器来控制所有服务器的请求。实现方式:
- 选择一个中心化的组件,例如—Redis。
- 定义限流规则,例如:可以设置每秒钟允许的最大请求数(QPS),并将这个值存储在Redis中。
- 对于每个请求,服务器需要先向Redis请求令牌。
- 如果获取到令牌,说明请求可以被处理;如果没有获取到令牌,说明请求被限流,可以返回一个错误信息或者稍后重试。
1 | package main |
2.2基于负载均衡的分布式限流
可以看到中心化限流方案的存在较高的单点故障风险,且带宽瓶颈比较严重。在这个基础上本文结合本地缓存单机限流和负载均衡设计了一个新的分布式限流方案。具体方案如下:
- 使用负载均衡器或分布式服务发现(北极星即可做到),将请求均匀地分发到每个机器上。这确保了每个机器都能处理一部分请求。
- 在每个机器上维护本机的限流状态,实现本地缓存单机限流的逻辑。使用令牌桶限流算法,在每个机器上独立地进行限流控制。每秒钟处理的请求数、令牌桶的令牌数量等。根据本地限流状态,对到达的请求进行限流判断。
- 准备相应的动态调整方案,可以根据每个机器的实际负载情况,动态地调整限流参数。例如,如果一个机器的CPU或内存使用率过高,你可以降低这个机器的限流阈值,减少这个机器的请求处理量。反之,如果一个机器的资源使用率较低,提高这个机器的限流阈值,增加这个机器的请求处理量。
2.3基于分布式协调服务的限流
使用ZooKeeper或者etcd等分布式协调服务来实现限流。每台服务器都会向分布式协调服务申请令牌,只有获取到令牌的请求才能被处理。基本方案:
- 初始化令牌桶:在ZooKeeper中创建一个节点,节点的数据代表令牌的数量。初始时,将数据设置为令牌桶的容量。
- 申请令牌:当一个请求到达时,服务器首先向ZooKeeper申请一个令牌。这可以通过获取节点的分布式锁,然后将节点的数据减1实现。如果操作成功,说明申请到了令牌,请求可以被处理;如果操作失败,说明令牌已经用完,请求需要被拒绝或者等待。
- 释放令牌:当一个请求处理完毕时,服务器需要向ZooKeeper释放一个令牌。这可以通过获取节点的分布式锁,然后将节点的数据加1实现。
- 补充令牌:可以设置一个定时任务,定期向ZooKeeper中的令牌桶补充令牌。补充的频率和数量可以根据系统的负载情况动态调整。
2.4方案总结
一个好的限流设计必须要考虑到业务的特性和需求,同时具备以下六点:
- 多级限流:除了主备复制的限流服务,可以考虑实现多级限流策略。例如,可以在应用层、服务层和数据层都设置限流,这样可以更好地防止系统过载。
- 动态阈值调整:我们可以根据系统的实时负载情况动态调整限流策略。例如,当系统负载较低时,我们可以放宽限流策略;当系统负载较高时,我们可以收紧限流策略。
- 灵活维度:限流策略应该能够根据不同的业务场景进行调整。除了接口,设备,IP,账户ID等维度外,我们还可以考虑更细粒度的限流。例如,我们可以根据用户的行为模式进行限流,这样可以更好地防止恶意用户的攻击。
- 解耦性:限流应该作为一个基础服务,与具体的业务逻辑分离。这样,当业务逻辑发生变化时,不需要修改限流服务的代码,只需要调整限流策略即可。
- 容错性:限流服务应该具有高可用性,但是如果出现问题,业务应该有备选方案(熔断、降级)。这可能包括使用备用的限流服务,或者根据业务的敏感性决定是否放行请求。
- 监控和报警:对限流策略应进行实时监控,并设置报警机制。当限流策略触发时,可立即收到报警,以便我们可以及时地处理问题。
3.配置中心
4.超时治理
分布式应⽤的挑战之⼀就是如何管理远程服务的可用性和它们的响应。本文主要探讨服务的响应时间对系统的影响和应对。
上图是简化的微服务调用链路过程,为清晰阐述三个相关方,图中的客户端被限定为用户端(如移动端应用、浏览器页面等),服务端被区分为服务消费方(网络调用中客户端)和服务提供方(网络调用中服务端)。⼤部分服务既为服务消费方,⼜为服务提供方,如处于调⽤链路中间的业务服务,大概率需要去整合数据,所以通常会同时作为服务消费方和服务提供方,两种资源消耗并存。小部分服务是纯粹的服务提供方,如数据库、缓存、ZooKeeper等。下⽂先来分析服务响应时间过⻓对资源消耗问题。
4.1资源消耗分析
4.1.1静态分析
微服务都有⾃身的硬件资源上限,直观来看,响应时间会对资源消耗产生直接影响。
- 服务消费方:
- 协议消耗,每次发起TCP连接请求时,通常会让系统选取⼀个空闲的本地端⼝,该端⼝是独占的,不能和其他TCP连接共享。TCP端⼝的数据类型是unsigned short,因此可⽤端⼝最多只有65535,所以在全部作为client端的情况下,最⼤TCP连接数65535。
- 除端口消耗外,发起调⽤的业务进程、线程、协程等待过程中,⽆法释放其所消耗的内存、CPU等,是服务消费方发起调用的主要消耗。
- 服务提供方:
- 协议消耗,主要是建立连接的消耗,每接收每⼀个TCP连接都要占⼀个文件描述符,理论上是server端单机最大TCP连接数约为2的48次方。
- 业务逻辑消耗。在复杂的业务逻辑、机器资源和网络带宽条件下,最大并发TCP连接数远不能达到理论上限,有时候会发现,单机1w并发往往也是比较困难。因此,服务提供方主要是业务逻辑的大量资源消耗,如CPU、网络带宽、磁盘IO等。
4.1.2动态分析
在调用持续发生且服务提供方不及时返回的情况下,未触发性能拐点前,可以简化认为资源的消耗是线性增长。
微服务发起⼀个请求,会占⽤⼀个空闲的本地端⼝,当然,每个连接所对应的业务处理过程,也会对应消耗内存、IO、CPU等资源,简化为如下公式:
RR = RT - QPS * RCPR * Duration - Release * Tinny_T
其中,资源容量(Resource Total)受单机性能影响,可以简化为固定值;单请求消耗资源数(ResourceCostPerRequest)受业务影响,也可以简化为固定值。那么,资源剩余数(Resource Remaining),将受这三个变量影响:每秒请求数(QPS)、请求持续时间 (Duration)和资源释放的速度(Release * Tinny_T)。在连接不释放的情境下,Release简化为0,则上⾯公式简化为:
RR = RT - QPS * RCPR * Duration
可以看到,系统所剩余资源,随Duration线性减少,最终被耗尽。
4.1.3设置超时
在设定超时时间后。由于资源释放速度较快,可以假设为⼀旦主动关闭连接,资源立即释放。那么,系统内所保留的资源可以简化为设置超时时间(Timeout Value)时间段内连接所保持的资源:
RR = RT - QPS * RCPR * TV
在资源总量、QPS固定、且发生超时的情况下,超时时间和资源消耗的速度和成近似线性正比。所以从尽快释放消耗资源的角度来看,超时时间的设置应当和QPS成反比,QPS越⾼,超时时间应当越短。
4.2超时时间设置治理
从资源静态和动态分析看,我们应当规范设置调⽤超时时间。设置超时时间的意义,是在极端情况下,采用主动的快速失败策略,使得资源消耗与释放资源之间达到平衡,避免调用双方因资源耗尽而宕机
。超时时间设置不当,容易引发生产故障,线上已经有诸多血的教训。
4.2.1设置过长
超时时间过长容易引起降级失效、系统崩溃、连接池爆满等问题。如下图,内容服务是面向用户直接提供知识内容的核心服务。收藏服务返回用户对内容的是否收藏,属于低优先级服务。如果内容服务请求收藏服务设置超时时间过长,如图中,虽然设置1S超时,但是重试累计3S,则⼀旦收藏服务发生宕机(通常是由于上游QPS异常导致),则内容服务会失效,无法返回给用户数据,甚⾄发生串联雪崩。
4.2.2设置过短
超时时间设置过短,实际生产中容易因网络抖动而告警频繁,造成服务不稳定等用户体验问题。如内容服务调⽤资源服务超时时间设置200ms,资源服务调用ID发号器服务超时时间设置为300ms,⼀旦网络抖动后,资源服务200ms即超时返回,资源服务对下游的调⽤300ms超时也无实际意义。
4.2.3合理设定
合理设置超时时间,对系统的稳定而言,非常重要,我们可以从以下角度来考虑设置值:
- 用户角度。微服务最终为服务对象是用户,
从⽤户交互数据来讲,服务响应时间在300ms内最佳
,2000ms内仍可接受。通常情况下,建议超时的上限值为2000ms,超过2000ms的非重要请求,则有必要被降级处理。 - 技术角度。同时考虑到TCP算法中
Delayed ACK + Nagle算法开启
的场景,最小delay值40ms
,建议下限值设定为50ms;在RTT较小的正常网络环境中,TCP数据包丢包,超时重传的最小值200ms
,因此我们建议300ms可以视为超时设置的最佳选择,为重传保留⼀定的余量。 - 资源消耗角度。依据资源消耗的分析,
超时时间长短应当和QPS成反比例
。我们设定基础值超时设定为300ms、100QPS,并根据实际QPS做调整。
4.3告警和优化
超时时间设置是系统的第⼀重保护,超时时间设置后,需要配合超时告警和响应时间优化,才能形成构成的完整的系统自我修复的闭环。
4.3.1告警
超时告警,会直接反馈超时设置的效果,而且也是长耗时请求被超时设置拦截后,系统自我保护的下⼀个重要环节。系统调用发生超时告警,可以通过运维体系监控告警让人力及时介入排查问题,以避免更大损失。
超时告警通常只是告警监控系统⾥⼀个子监控项,从实际生产中来看,超时告警往往也是最频繁发生的告警,超时告警如何在保障灵敏度的同时,避免过度告警,需要专门考虑。对此,我们建议从范围和时间两个维度来区分超时告警:
从范围来看,可以将局部性超时
和大面积超时告警
区分对待:
大面积告警,紧急人力介入,通常是运维网络故障等原因,可以结合网络监控来具体定位。
局部小范围告警,作为非紧急重要事情处理。
从时间来看,将波动性
和持续性
超时告警区分对待:
- 持续性问题,大概率是业务问题,紧急人力介入从而避免业务发生重大损失。
- 波动性问题,根据出现频率,排查隐藏线上问题并解决,作为非紧急重要事情处理。
对于如何定义短暂性的波动,可以由运维评估出日常
网络平均抖动时长
,超过网络抖动时长⼀定阈值后,则触发紧急告警。如果运维体系资源充足,建议将超时告警波动性监控细化到具体接口。
4.3.2优化
响应时间优化是超时治理的治本措施。理想情况下,服务的响应时间当然是越快越好,但也要和当前业务发展相匹配,要保障系统稳定,也要避免为优化响应时间耗费过多成本。
原则上,应当依据超时设置时间来优化响应时间,而非以响应时间来决定超时时间。
服务超时,可能是应用层代码错误、网络丢包,服务器自身硬件异常、或大量数据库操作慢查询等问题导致。可以采⽤的措施有:
- 排查异常问题,如排查业务超时重试次数、排查错误日志、排查库表操作是否有慢查询等。
- 调整软件设计架构,提高业务响应速度,如使用缓存、拆分调整架构减少调用层级等。
- 调整运维方式,如发版引发的告警,则可以从调用框架的重试中进行解决,也可以依赖滚动发布进行解决。