Java问答知识总结篇-场景分析题


Java问答知识总结篇-基础知识
Java问答知识总结篇-JVM
Java问答知识总结篇-多线程&并发编程
Java问答知识总结篇-网络基础
Java问答知识总结篇-Spring
Java问答知识总结篇-Spring Boot
Java问答知识总结篇-Mybatis
Java问答知识总结篇-MySQL
Java问答知识总结篇-Redis
Java问答知识总结篇-MQ
Java问答知识总结篇-Nginx
Java问答知识总结篇-分布式
Java问答知识总结篇-Spring Cloud
Java问答知识总结篇-Dubbo
Java问答知识总结篇-Zookeeper
Java问答知识总结篇-ElasticSearch
Java问答知识总结篇-Netty
Java问答知识总结篇-场景分析题

硬件故障排查

如果一个实例发生了问题,根据情况选择,要不要着急去重启。如果出现的CPU、内存飙高或者日志里出现了OOM异常

第一步是隔离,第二步是保留现场,第三步才是问题排查

隔离

就是把你的这台机器从请求列表里摘除,比如把 nginx 相关的权重设成零。

现场保留

瞬时态和历史态

查看比如 CPU、系统内存等,通过历史状态可以体现一个趋势性问题,而这些信息的获取一般依靠监控系统的协作。

保留信息

  1. 系统当前网络连接
ss -antp > $DUMP_DIR/ss.dump 2>&1

使用 ss 命令而不是 netstat 的原因,是因为 netstat 在网络连接非常多的情况下,执行非常缓慢。

后续的处理,可通过查看各种网络连接状态的梳理,来排查 TIME_WAIT 或者 CLOSE_WAIT,或者其他连接过高的问题,非常有用。

  1. 网络状态统计
netstat -s > $DUMP_DIR/netstat-s.dump 2>&1

它能够按照各个协议进行统计输出,对把握当时整个网络状态,有非常大的作用。

sar -n DEV 1 2 > $DUMP_DIR/sar-traffic.dump 2>&1

在一些速度非常高的模块上,比如 Redis、Kafka,就经常发生跑满网卡的情况。表现形式就是网络通信非常缓慢。

  1. 进程资源
lsof -p $PID > $DUMP_DIR/lsof-$PID.dump

通过查看进程,能看到打开了哪些文件,可以以进程的维度来查看整个资源的使用情况,包括每条网络连接、每个打开的文件句柄。同时,也可以很容易的看到连接到了哪些服务器、使用了哪些资源。这个命令在资源非常多的情况下,输出稍慢,请耐心等待。

  1. CPU 资源
mpstat > $DUMP_DIR/mpstat.dump 2>&1
vmstat 1 3 > $DUMP_DIR/vmstat.dump 2>&1
sar -p ALL  > $DUMP_DIR/sar-cpu.dump  2>&1
uptime > $DUMP_DIR/uptime.dump 2>&1

主要用于输出当前系统的 CPU 和负载,便于事后排查。

  1. I/O 资源
iostat -x > $DUMP_DIR/iostat.dump 2>&1

一般,以计算为主的服务节点,I/O 资源会比较正常,但有时也会发生问题,比如日志输出过多,或者磁盘问题等。此命令可以输出每块磁盘的基本性能信息,用来排查 I/O 问题。在第 8 课时介绍的 GC 日志分磁盘问题,就可以使用这个命令去发现。

  1. 内存问题
free -h > $DUMP_DIR/free.dump 2>&1

free 命令能够大体展现操作系统的内存概况,这是故障排查中一个非常重要的点,比如 SWAP 影响了 GC,SLAB 区挤占了 JVM 的内存。

  1. 其他全局
ps -ef > $DUMP_DIR/ps.dump 2>&1
dmesg > $DUMP_DIR/dmesg.dump 2>&1
sysctl -a > $DUMP_DIR/sysctl.dump 2>&1

dmesg 是许多静悄悄死掉的服务留下的最后一点线索。当然,ps 作为执行频率最高的一个命令,由于内核的配置参数,会对系统和 JVM 产生影响,所以我们也输出了一份。

  1. 进程快照,最后的遗言(jinfo)
${JDK_BIN}jinfo $PID > $DUMP_DIR/jinfo.dump 2>&1

此命令将输出 Java 的基本进程信息,包括环境变量和参数配置,可以查看是否因为一些错误的配置造成了 JVM 问题。

  1. dump 堆信息
${JDK_BIN}jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil.dump 2>&1
${JDK_BIN}jstat -gccapacity $PID > $DUMP_DIR/jstat-gccapacity.dump 2>&1

jstat 将输出当前的 gc 信息。一般,基本能大体看出一个端倪,如果不能,可将借助 jmap 来进行分析。

  1. 堆信息
${JDK_BIN}jmap $PID > $DUMP_DIR/jmap.dump 2>&1
${JDK_BIN}jmap -heap $PID > $DUMP_DIR/jmap-heap.dump 2>&1
${JDK_BIN}jmap -histo $PID > $DUMP_DIR/jmap-histo.dump 2>&1
${JDK_BIN}jmap -dump:format=b,file=$DUMP_DIR/heap.bin $PID > /dev/null  2>&1

jmap 将会得到当前 Java 进程的 dump 信息。如上所示,其实最有用的就是第 4 个命令,但是前面三个能够让你初步对系统概况进行大体判断。因为,第 4 个命令产生的文件,一般都非常的大。而且,需要下载下来,导入 MAT 这样的工具进行深入分析,才能获取结果。这是分析内存泄漏一个必经的过程。

  1. JVM 执行栈
${JDK_BIN}jstack $PID > $DUMP_DIR/jstack.dump 2>&1

jstack 将会获取当时的执行栈。一般会多次取值,我们这里取一次即可。这些信息非常有用,能够还原 Java 进程中的线程情况。

top -Hp $PID -b -n 1 -c >  $DUMP_DIR/top-$PID.dump 2>&1

为了能够得到更加精细的信息,我们使用 top 命令,来获取进程中所有线程的 CPU 信息,这样,就可以看到资源到底耗费在什么地方了。

  1. 高级替补
kill -3 $PID

有时候,jstack 并不能够运行,有很多原因,比如 Java 进程几乎不响应了等之类的情况。我们会尝试向进程发送 kill -3 信号,这个信号将会打印 jstack 的 trace 信息到日志文件中,是 jstack 的一个替补方案。

gcore -o $DUMP_DIR/core $PID

对于 jmap 无法执行的问题,也有替补,那就是 GDB 组件中的 gcore,将会生成一个 core 文件。我们可以使用如下的命令去生成 dump:

${JDK_BIN}jhsdb jmap --exe ${JDK}java  --core $DUMP_DIR/core --binaryheap

内存泄漏的现象

稍微提一下 jmap 命令,它在 9 版本里被干掉了,取而代之的是 jhsdb,你可以像下面的命令一样使用。

jhsdb jmap  --heap --pid  37340
jhsdb jmap  --pid  37288
jhsdb jmap  --histo --pid  37340
jhsdb jmap  --binaryheap --pid  37340

一般内存溢出,表现形式就是 Old 区的占用持续上升,即使经过了多轮 GC 也没有明显改善。比如ThreadLocal里面的GC Roots,内存泄漏的根本就是,这些对象并没有切断和 GC Roots 的关系,可通过一些工具,能够看到它们的联系。

报表异常 | JVM调优

有一个报表系统,频繁发生内存溢出,在高峰期间使用时,还会频繁的发生拒绝服务,由于大多数使用者是管理员角色,所以很快就反馈到研发这里。

业务场景是由于有些结果集的字段不是太全,因此需要对结果集合进行循环,并通过 HttpClient 调用其他服务的接口进行数据填充。使用 Guava 做了 JVM 内缓存,但是响应时间依然很长。

初步排查,JVM 的资源太少。接口 A 每次进行报表计算时,都要涉及几百兆的内存,而且在内存里驻留很长时间,有些计算又非常耗 CPU,特别的“吃”资源。而我们分配给 JVM 的内存只有 3 GB,在多人访问这些接口的时候,内存就不够用了,进而发生了 OOM。在这种情况下,没办法,只有升级机器。把机器配置升级到 4C8G,给 JVM 分配 6GB 的内存,这样 OOM 问题就消失了。但随之而来的是频繁的 GC 问题和超长的 GC 时间,平均 GC 时间竟然有 5 秒多。

进一步,由于报表系统和高并发系统不太一样,它的对象,存活时长大得多,并不能仅仅通过增加年轻代来解决;而且,如果增加了年轻代,那么必然减少了老年代的大小,由于 CMS 的碎片和浮动垃圾问题,我们可用的空间就更少了。虽然服务能够满足目前的需求,但还有一些不太确定的风险。

第一,了解到程序中有很多缓存数据和静态统计数据,为了减少 MinorGC 的次数,通过分析 GC 日志打印的对象年龄分布,把 MaxTenuringThreshold 参数调整到了 3(特殊场景特殊的配置)。这个参数是让年轻代的这些对象,赶紧回到老年代去,不要老呆在年轻代里。

第二,我们的 GC 时间比较长,就一块开了参数 CMSScavengeBeforeRemark,使得在 CMS remark 前,先执行一次 Minor GC 将新生代清掉。同时配合上个参数,其效果还是比较好的,一方面,对象很快晋升到了老年代,另一方面,年轻代的对象在这种情况下是有限的,在整个 MajorGC 中占的时间也有限。

第三,由于缓存的使用,有大量的弱引用,拿一次长达 10 秒的 GC 来说。我们发现在 GC 日志里,处理 weak refs 的时间较长,达到了 4.5 秒。这里可以加入参数 ParallelRefProcEnabled 来并行处理Reference,以加快处理速度,缩短耗时。

优化之后,效果不错,但并不是特别明显。经过评估,针对高峰时期的情况进行调研,我们决定再次提升机器性能,改用 8core16g 的机器。但是,这带来另外一个问题。

高性能的机器带来了非常大的服务吞吐量,通过 jstat 进行监控,能够看到年轻代的分配速率明显提高,但随之而来的 MinorGC 时长却变的不可控,有时候会超过 1 秒。累积的请求造成了更加严重的后果。

这是由于堆空间明显加大造成的回收时间加长。为了获取较小的停顿时间,我们在堆上改用了 G1 垃圾回收器,把它的目标设定在 200ms。G1 是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的 GC 暂停目标,就能得到不错的性能。修改之后,虽然 GC 更加频繁了一些,但是停顿时间都比较小,应用的运行较为平滑。

到目前为止,也只是勉强顶住了已有的业务,但是,这时候领导层面又发力,要求报表系统可以支持未来两年业务10到100倍的增长,并保持其可用性,但是这个“千疮百孔”的报表系统,稍微一压测,就宕机,那如何应对十倍百倍的压力呢 ? 硬件即使可以做到动态扩容,但是毕竟也有极限。

使用 MAT 分析堆快照,发现很多地方可以通过代码优化,那些占用内存特别多的对象:

  1. select * 全量排查,只允许获取必须的数据

  2. 报表系统中cache实际的命中率并不高,将Guava 的 Cache 引用级别改成弱引用(WeakKeys)

  3. 限制报表导入文件大小,同时拆分用户超大范围查询导出请求。

每一步操作都使得JVM使用变得更加可用,一系列优化以后,机器相同压测数据性能提升了数倍。

大屏异常 | JUC调优

有些数据需要使用 HttpClient 来获取进行补全。提供数据的服务提供商有的响应时间可能会很长,也有可能会造成服务整体的阻塞。

接口 A 通过 HttpClient 访问服务 2,响应 100ms 后返回;接口 B 访问服务 3,耗时 2 秒。HttpClient 本身是有一个最大连接数限制的,如果服务 3 迟迟不返回,就会造成 HttpClient 的连接数达到上限,概括来讲,就是同一服务,由于一个耗时非常长的接口,进而引起了整体的服务不可用

这个时候,通过 jstack 打印栈信息,会发现大多数竟然阻塞在了接口 A 上,而不是耗时更长的接口 B,这个现象起初十分具有迷惑性,不过经过分析后,我们猜想其实是因为接口 A 的速度比较快,在问题发生点进入了更多的请求,它们全部都阻塞住的同时被打印出来了。

为了验证这个问题,我搭建了一个demo 工程,模拟了两个使用同一个 HttpClient 的接口。fast 接口用来访问百度,很快就能返回;slow 接口访问谷歌,由于众所周知的原因,会阻塞直到超时,大约 10 s。 利用ab对两个接口进行压测,同时使用 jstack 工具 dump 堆栈。首先使用 jps 命令找到进程号,然后把结果重定向到文件(可以参考 10271.jstack 文件)。

过滤一下 nio 关键字,可以查看 tomcat 相关的线程,足足有 200 个,这和 Spring Boot 默认的 maxThreads 个数不谋而合。更要命的是,有大多数线程,都处于 BLOCKED 状态,说明线程等待资源超时。通过grep fast | wc -l 分析,确实200个中有150个都是blocked的fast的进程。

问题找到了,解决方式就顺利成章了。

  1. fast和slow争抢连接资源,通过线程池限流或者熔断处理

  2. 有时候slow的线程也不是一直slow,所以就得加入监控

  3. 使用带countdownLaunch对线程的执行顺序逻辑进行控制

接口延迟 | SWAP调优

有一个关于服务的某个实例,经常发生服务卡顿。由于服务的并发量是比较高的,每多停顿 1 秒钟,几万用户的请求就会感到延迟。

我们统计、类比了此服务其他实例的 CPU、内存、网络、I/O 资源,区别并不是很大,所以一度怀疑是机器硬件的问题。

接下来我们对比了节点的 GC 日志,发现无论是 Minor GC,还是 Major GC,这个节点所花费的时间,都比其他实例长得多。

通过仔细观察,我们发现在 GC 发生的时候,vmstat 的 si、so 飙升的非常严重,这和其他实例有着明显的不同。

使用 free 命令再次确认,发现 SWAP 分区,使用的比例非常高,引起的具体原因是什么呢?

更详细的操作系统内存分布,从 /proc/meminfo 文件中可以看到具体的逻辑内存块大小,有多达 40 项的内存信息,这些信息都可以通过遍历 /proc 目录的一些文件获取。我们注意到 slabtop 命令显示的有一些异常,dentry(目录高速缓冲)占用非常高。

问题最终定位到是由于某个运维工程师删除日志时,定时执行了一句命令:

find / | grep "xxx.log"

他是想找一个叫做 要被删除 的日志文件,看看在哪台服务器上,结果,这些老服务器由于文件太多,扫描后这些文件信息都缓存到了 slab 区上。而服务器开了 swap,操作系统发现物理内存占满后,并没有立即释放 cache,导致每次 GC 都要和硬盘打一次交道。

解决方式就是关闭 SWAP 分区。

swap 是很多性能场景的万恶之源,建议禁用。在高并发 SWAP 绝对能让你体验到它魔鬼性的一面:进程倒是死不了了,但 GC 时间长的却让人无法忍受。

内存溢出 | Cache调优

有一次线上遇到故障,重新启动后,使用 jstat 命令,发现 Old 区一直在增长。我使用 jmap 命令,导出了一份线上堆栈,然后使用 MAT 进行分析,通过对 GC Roots 的分析,发现了一个非常大的 HashMap 对象,这个原本是其他同事做缓存用的,但是做了一个无界缓存,没有设置超时时间或者 LRU 策略,在使用上又没有重写key类对象的hashcode和equals方法,对象无法取出也直接造成了堆内存占用一直上升,后来,将这个缓存改成 guava 的 Cache,并设置了弱引用,故障就消失了。

关于文件处理器的应用,在读取或者写入一些文件之后,由于发生了一些异常,close 方法又没有放在 finally 块里面,造成了文件句柄的泄漏。由于文件处理十分频繁,产生了严重的内存泄漏问题。

内存溢出是一个结果,而内存泄漏是一个原因。内存溢出的原因有内存空间不足、配置错误等因素。一些错误的编程方式,不再被使用的对象、没有被回收、没有及时切断与 GC Roots 的联系,这就是内存泄漏。

举个例子,有团队使用了 HashMap 做缓存,但是并没有设置超时时间或者 LRU 策略,造成了放入 Map 对象的数据越来越多,而产生了内存泄漏。

再来看一个经常发生的内存泄漏的例子,也是由于 HashMap 产生的。代码如下,由于没有重写 Key 类的 hashCode 和 equals 方法,造成了放入 HashMap 的所有对象都无法被取出来,它们和外界失联了。所以下面的代码结果是 null。

//leak example
import java.util.HashMap;
import java.util.Map;
public class HashMapLeakDemo {
    public static class Key {
        String title;
    public Key(String title) {
        this.title = title;
    }
}

public static void main(String[] args) {
    Map<Key, Integer> map = new HashMap<>();
    map.put(new Key("1"), 1);
    map.put(new Key("2"), 2);
    map.put(new Key("3"), 2);
    Integer integer = map.get(new Key("2"));
    System.out.println(integer);
    }
}

即使提供了 equals 方法和 hashCode 方法,也要非常小心,尽量避免使用自定义的对象作为 Key。

再看一个例子,关于文件处理器的应用,在读取或者写入一些文件之后,由于发生了一些异常,close 方法又没有放在 finally 块里面,造成了文件句柄的泄漏。由于文件处理十分频繁,产生了严重的内存泄漏问题。

CPU飙高 | 死循环

我们有个线上应用,单节点在运行一段时间后,CPU 的使用会飙升,一旦飙升,一般怀疑某个业务逻辑的计算量太大,或者是触发了死循环(比如著名的 HashMap 高并发引起的死循环),但排查到最后其实是 GC 的问题。

  1. 使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift + P 快捷键可以按 CPU 的使用率进行排序。
top
  1. 再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。
top -Hp $pid
  1. 使用 printf 函数,将十进制的 tid 转化成十六进制。
printf %x $tid
  1. 使用 jstack 命令,查看 Java 进程的线程栈。
jstack $pid >$pid.log
  1. 使用 less 命令查看生成的文件,并查找刚才转化的十六进制 tid,找到发生问题的线程上下文。
less $pid.log

我们在 jstack 日志搜关键字DEAD,以及中找到了 CPU 使用最多的几个线程id。

可以看到问题发生的根源,是我们的堆已经满了,但是又没有发生 OOM,于是 GC 进程就一直在那里回收,回收的效果又非常一般,造成 CPU 升高应用假死。接下来的具体问题排查,就需要把内存 dump 一份下来,使用 MAT 等工具分析具体原因了。

根据项目聊技术应用

假设下面是当前项目的基本架构图,根据架构图衍生出一系列的问题。在面试的时候,很多面试官都是会根据你实际的项目来逐渐深入。

  1. 简单介绍一下你参与的XX项目

XX项目是由我来主导设计和开发的。开始的时候这个项目采用的是JSP+Spring+Spring MVC+Mybatis的架构,前端框架比较陈旧,前后端也未做分离,所有的业务都是糅合在一起的,具体需要开放哪些功能,取决于项目部署在什么位置,需要实现哪些业务。整个项目各个模块必须绑定一起部署,即使在当前的环境下不使用的功能。整个项目是典型的单体项目,且前后端未做分离。

后期我接手此项目后,着手对项目进行重构。将前端由原来的JSP更换为VUE,同时做了前后端分离。后端采用了SpringBoot+Spring+Mybatis的技术架构,将原来的单体项目切分成了四个微服务。分别是负责数据采集和云端同步的数采模块、负责数据分析入库的模块、算法服务模块以及数据应用模块。后三个模块是基于Spring Cloud Alibaba构建的微服务,采用了Nacos注册中心和配置中心,Ribbon负载均衡,OpenFeign的服务调用等。另外在数采模块和数据分析模块间使用了RabbitMQ消息队列,实现了解耦合,同时也避免了负责的网络环境带来的不必要的麻烦。数采模块的主要作用是采集采集装置的数据并将数据上传到云端OSS对象存储环境中。

  1. 采集装置如何与数采系统交互

数采系统通过socket与采集装置做长链接,已数据流的方式,实时读取采集装置内的数据,按照10秒时间片段缓存到本地,同时将片段文件上传到OSS云存储环境中。

  1. socket实现的基本原理是什么样的,如何出现中断如何处理

socket本身不是协议,是基于TCP/IP的传输,通过IP和端口,远程连接目前服务,连接的过程通过TCP的三次握手机制,确认连接状态,连接成功后,通过以字节流的形式读取目标服务内的数据。(这里结束肯定会为三次握手的机制,问完了甚至还会顺便问一下四次挥手,这里要抗住,基础知识部分要扎实)

因为音频本身的数据是24小时不间断的,可以直接根据当前连接是否有正常的数据进入以及连接状态来确定连接时候出现异常断开的现象。如果出现断开,将会自动关闭socket连接,将断开的链接信息缓存,进入重试连接状态。同时也会形成连接的状态信息同步到在线监测系统,做相应的展示和提示。

  1. 为何要是用RabbitMQ消息队列,直接调用不好吗

首先数采系统是部署在各个变电站的,在不同的省市地区,网络稳定性不稳定,另外涉及跨省市之间的网络贯通,存在很长的审批流程,后期接入变电站变多,整体复杂度较高,而且不易维护。然后就是直接调用耦合度太高。

采用RabbitMQ能解决很多问题。首先数据同步,所有的变电站只要统一与RabbitMQ交互即可,无需关系数据分析服务所在的位置。然后就是繁杂的网络开通过程被简化啦。

  1. 如何保证RabbitMQ消息不丢失和顺序

参考MQ篇中的相关内容,固定套路(发送消息的ACK、队列和交换机的持久化、消费者的ACK,如果是保证顺序性,就采用单生产者、单消费者、单队列即可)

  1. Spring Cloud Alibaba相关知识点

参考Spring Cloud篇中的相关内容,固定套路(nacos注册中心和配置中心,永不上sentinel,应为整个系统音频上传的最大QPS是可以预测,能够提前根据QPS对系统进行扩容)

  1. 数据分析模块和算法分析模块为何不放在一起,采用调用的方式

第一,算法服务这个模块并不是我们团队负责研发的,因为这块涉及声音类分析的专业知识领域的内容,当时是委托第三方做的;
第二,系统间职责分明,将算法服务作为一个单纯的算法来设立,不去涉及业务相关的功能,保证算法研发这块单纯性,分析音频,给到结果即可,由我们自己研发数据分析模块,做数据的相关管理。

  1. 数采模块为何不做到微服务架构中

因为数采系统不是集中部署,而是分散在各个省市变电站,都是采用各自的内网环境,不能互通。

redo、undo、binlog的生成流程与崩溃恢复

当我们执行update user_info set name ="李四" where id=1的时候大致流程如下:

  1. 从磁盘读取到id=1的记录,放到内存;
  2. 记录undo log 日志;
  3. 记录redo log (预提交状态);(磁盘数据变更日志)
  4. 修改内存中的记录;
  5. 记录binlog;
  6. 提交事务,写入redo log (commit状态)

我们根据上面的流程来看,如果在上面的某一个阶段数据库崩溃,如何恢复数据。

  1. 在第一步、第二步、第三步执行时据库崩溃:因为这个时候数据还没有发生任何变化,所以没有任何影响,不需要做任何操作。

  2. 在第四步修改内存中的记录时数据库崩溃:因为此时事务没有commit,所以这里要进行数据回滚,所以这里会通过undo log进行数据回滚。

  3. 第五步写入binlog时数据库崩溃:这里和第四步一样的逻辑,此时事务没有commit,所以这里要进行数据回滚,会通过undo log进行数据回滚。

  4. 执行第六步事务提交时数据库崩溃:如果数据库在这个阶段崩溃,那其实事务还是没有提交成功,但是这里并不能像之前一样对数据进行回滚,因为在提交事务前,binlog可能成功写入磁盘了,所以这里要根据两种情况来做决定。
    如果binlog存在事务记录,那么就”认为”事务已经提交了,这里可以根据redo log对数据进行重做。其实你应该有疑问,其实这个阶段发生崩溃了,最终的事务是没提交成功的,这里应该对数据进行回滚。 这里主要的一个考虑是因为binlog已经成功写入了,而binlog写入后,那么依赖于binlog的其它扩展业务(比如:从库已经同步了日志进行数据的变更)数据就已经产生了,如果这里进行数据回滚,那么势必就会造成主从数据的不一致。

另外一种情况就 是binlog不存在事务记录,那么这种情况事务还未提交成功,所以会对数据进行回滚。

Spring和Spring Boot的启动流程

Spring启动流程
Spring Boot启动流程

nacos原理和OpenFeign的原理

Nacos原理
openFeign原理

消息一致性问题

1、消息发送成功
可以通过MQ自身的ACK机制,在发送消息的时候通过异步接收是否发送成功,如果没有收到,做重发动作。
2、中间件的消息丢失问题
首先是要开启MQ的持久化功能,另外MQ的ACK机制中,返回成功则表示消息已经持久化成功,保证消息在中间宕机、网络异常等,不会发生消息丢失问题。
3、消息未消费和重复消费
建立队列和消费者的反馈机制,消费者消费后给MQ中间件反馈消费情况。
4、事务回滚
在消费失败的时候,如果涉及事务回滚,可以通过Redis实现消费者和生产者的交互,出现异常,及时通知和回滚。

支付接口安全性怎么保证

第一:数据传输的安全性

1、交互过程由正常的HTTP请求修改成HTTPS请求;
2、在客户端发送请求钱对其进行身份的验证,验证身份的token需要做失效处理,防止被抓包、爬取;
3、传输的数据,根据参数、客户端应用的信息、时间戳等信息生成签名,使用MD5加密;
4、签名主要是保证参数在传输途中不会被篡改,时间戳用来做时间上的限制,防止被暴力破解。

第二:防止重复支付,保证幂等性

防止客户误操作,多次点击支付功能,产生多条支付的问题,可以根据订单的编号,利用锁机制保证幂等性。在集群中可以考虑使用分布式锁,在单机模式下,可以考虑使用悲观锁机制。

如果现在有100张票,如何保证出票的安全

1、单机服务使用悲观锁,进而引出乐观锁

如果单机服务,可以使用悲观锁synchronized实现,能够保证安全,但是要等待其他线程执行结束后才能进入,效率较低。这里其实也可以使用乐观锁,使用原子类实现,也能保证安全。

2、集群服务采用分布式锁,进而引出Redis分布式锁

如果是集群服务,synchronized和原子类是无法实现的,这个时候可以引入Redis,Redis能够保证命令的顺序性和原子性,另外根据Redis的单线程命令处理机制,能够做到线程安全。

CPU高的相关问题

CPU突然飚高,用户请求响应变慢问题

CPU突然飚高原因

  • CPU上下文切换过于频繁
    • 文件IO
    • 网络IO
    • 锁等待
    • 线程阻塞
  • CPU资源被长时间占用
    • 创建大量的线程
    • 单个线程占用资源不释放(比如死循环)

因为单个CPU只能运行一个线程,这个时候如果线程过多,就会导致CPU上下文频繁的切换,在切换过程中,会对运行线程的状态进行保存挂起,同时会去执行即将运行的线程,在整个过程会执行一系列的指令,从而导致CPU资源大量被占用,用户线程的指令得不到执行,进而引起用户请求响应慢的问题。

CPU缓慢升高问题

在应用中主要占用CPU资源的是线程,包括线程的执行,线程上下文的切换。如果是缓慢升高,可以排除大并发量的请求导致,可以从线程占用资源角度来看,主要有线程执行占用资源时间长,不释放,此时线程又不断的在创建,导致CPU缓慢的升高问题。这种情况主要是编码级别的问题,这个时候可以通过查看服务器的进程,查看占用资源较多且不释放的线程对应的代码块,分析并解决原因。

CPU飚高的排查步骤

1、首先使用top命令检查当前CPU占用率高的应用,并记录此应用的PID,因为一个服务器中可能会部署多个系统,这个时候要去确定是哪个系统导致的CPU飙升问题;
2、通过ps -m PID -o thread,tid,time命令获取当前进程内的所有线程,通过线程的CPU占用率来确定占用最高的线程TID;
3、这里的TID是十进制的数值,通过printf将其转换成十六进制,得到最终的线程ID;
4、使用jstack命令查看当前线程的运行的异常日志,如果日志文件较大,可以通过导出的方式,通过本地查看分析。

如果是单个线程问题持续占用CPU:需要检查代码是否存在不合理的地方,进行bug修复;
如果是多个线程切换的占用CPU资源:可能是单纯的用户量突然升高导致的服务器CPU资源不够的问题。

检查死锁、内存飚高的原因和解决方案

秒杀场景解决方案

正常在秒杀活动上线的时候,会对系统QPS进行压测,根据用户量的预测,能达到10W的QPS就能满足要求,但是实际上线后,出现超过10W的QPS,导致出现服务器被压垮。如果这个时候去动态推展服务已经来不及了,在拓展的过程中服务是不可用的,如果拓展过程中存在问题,也会带来很大的损失。

解决方案:

因为突发流量的原因导致的问题,那就从流量上入手,可以考虑加入限流的策略。在大流量的时候,对于超过的流量不做处理,直接给出友好提示的返回信息。这个时候需要在系统中建立流量预警,当出现预警的时候,再去进行动态的扩容,这样能在保证原系统服务正常服务的情况下,进行增强。
限流策略目前主流的有固定窗口算法、滑动窗口算法、令牌桶算法和漏桶算法。(说一说各种算法的机制)


文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
 上一篇
Java面试知识总结篇(持续更新) Java面试知识总结篇(持续更新)
Java基础是体现个人基础能力的核心,涉及很多方面,做个总结,将常用常被问到的基础知识整理出来,供大家评鉴。
2022-09-21
下一篇 
Java问答知识总结篇-Netty Java问答知识总结篇-Netty
以问答的方式构建Java知识体系,另外在问答中提供技术博客的支撑,更具体的解析和剖析内部深度知识点,做一个有深度的问答总结集。内容包括Java基础知识、JVM、Spring、Spring Cloud && Spring Cloud Alibaba、Mybatis、Spring Boot、Mybatis、Redis、MySQL等等。
2022-09-21
  目录