场景问题

杨大大...大约 13 分钟

缓存一致性解决

4G数据找系统记录(大数据题)

很多短任务线程,选择 synchronized 还是 lock(2022-04-11 携程)

锁竞争小时,synchronized和lock效率没差,偏向模式下(单线程读写)甚至高于lock,但是并发量上升时锁撤销会大幅影响性能,稳定自适应轻量级锁状态下,线程接近交替运行,或者说短任务线程多,基本一样,因为都是自旋,大量任务并发竞争时,随着任务量的增大,synchronized的效率会远小于lock,因为重量级锁会频繁切换内核态与用户态;大量长任务,只能重量级锁。

多个人给一个主播打赏怎么设计?(2022-6-3 58同城)

我说是一个高并发写的操作,对一个记录频繁写,分批操作,比如 10 个记录 操作一次。他说这个方案可以 但是有 100 个记录 怎么去做一个一个操作呢?我说如果在一个进程可以 分多个线程分批。他说还是不够快 我们是用的 MQ 多个消费者 一个打赏就发一个消息

怎么实现一个点赞功能?

主要的流程解释下:先查询数据库改用户是否进行点赞,如果已经点赞则抛出异常,如果没有则new一个对象来一个一个Set,然后将已点赞的信息存入redis中,相反,取消点赞的操作就是删除redis中的数据即可,然后通过Dubbo调用API来完成保存操作,因为我这里是还要获取点赞数和评论数啥的,所以会对动态表进行更新操作。

比如下单清空购物车,你是如何设计的?

  1. 生产者(订单系统)产生消息,发送一条半事务消息到MQ服务器
  2. MQ收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。
  3. MQ服务器返回ACK确认到生产者,此时MQ不会触发消息推送事件
  4. 生产者执行本地事务(订单创建成功,提交事务消息)
  5. 如果本地事务执行成功,即commit执行结果到MQ服务器;如果执行失败,发送rollback。
  6. 如果是commit正常提交,MQ服务器更新消息状态为可发送;如果是rollback,即删除消息
  7. 如果消息状态更新为可发送,则MQ服务器会push消息给消费者(购物车系统)。消费者消费完(即拿到订单消息,清空购物车成功)就应答ACK。
  8. 如果MQ服务器长时间没有收到生产者的commit或者rollback,它会反查生产者,然后根据查询到的结果(回滚操作或者重新发送消息)执行最终状态。

有些伙伴可能有疑惑,如果消费者消费失败怎么办呢?那数据是不是不一致啦?所以就需要消费者消费成功,执行业务逻辑成功,再反馈ack嘛。如果消费者消费失败,那就自动重试嘛,接口支持幂等即可。

排行榜的实现,比如高考成绩排序(2022 虾皮)

排行版的实现,一般使用redis的zset数据类型。

zadd key score member [score member ...],zrank key member

有一批帖子,会根据类别搜索,但是现在是单独一个表,现在查询非常慢,如何提高搜索性能?(2022 虾皮)

根据类别分库分表,库可以放到不同的实例上,经常查询的不变的数据]可以放到缓存里。 数据有更新时,需要刷新下缓存 因为分表后,只能是固定类别,所以需要根据类别去分开查找。 如果还有另一个重要的字段也需要查,可以再建一个分表,user-ses/ses-user就是这么做的,但是冗余就比较大。

如果有多个表,进行聚合查询,如何解决深分页的问题(2022 虾皮)

就是保存每个节点的表id给前端,前端查询时把id返回过来了,然后加到SQL里,但是不一定准。这里回答的是单个表吧。

分表的数据,动态增加一张表,不停服如何实现?(2022 虾皮)

分区策略使用一致性哈希 然后新表的数据,查询的时候,先查老的,再插入新的。如果老数据没有动,需要有对应的迁移服务进行定时迁移。插入的时候优先插入到新的表。

迁移线程和用户线程同时执行,会有数据库不一致的问题,怎么解决?(2022 虾皮)

加分布式锁。

两个机房,某个机房可能断电,如何做多机房容灾(2022 虾皮)

负载均衡层,支持切换机房写数据的时候,中间件(db/redis/es)都要进行双写。

kafka容灾,mirror maker: https://cloud.tencent.com/developer/article/1358933open in new window

主从机房同步有什么问题呢? (2022 虾皮)

会有比较大的延迟。 一些分布式的问题,例如分布式事务,可能就执行了几步,然后就挂了,需要有一定的策略,进行回滚或者提交。 切换机房的过程中,可能存在数据丢失,重复数据等

  • 双向同步,两个机房都能写入,如果操作的是各自的数据的话,问题不大。如果操作的是相同数据,必然会有冲突,需要解决。所以上层保证相同数据到同一个机房即可,然后同步到另外一个机房,保证每个机房都有全量的数据。各种中间件都要做改造。
  • 总之,分片的核心思路在于,让同一个用户的相关请求,只在一个机房内完成所有业务「闭环」,不再出现「跨机房」访问。
  • 阿里在实施这种方案时,给它起了个名字,叫做「单元化」。
  • 这里还有一种情况,是无法做数据分片的:全局数据。例如系统配置、商品库存这类需要强一致的数据,这类服务依旧只能采用写主机房,读从机房的方案,不做双活。
  • 双活的重点,是要优先保证「核心」业务先实现双活,并不是「全部」业务实现双活。

冷机房新请求过来,发现缓存没有,会把数据库打挂,这个怎么解决?(2022 虾皮)

预热,提前加载到缓存。 或者平时保持一定的流量。 用了缓存的,一般需要预热下,防止雪崩。

定时任务这种,怎么改变执行的机房(2022 虾皮)

加开关,任何时候都有一个条件不满足,在空跑。

自己

1.上传文件时该如何构造请求报文?(科大讯飞)

对于上传文件,客户端需要构造一个HTTP POST请求,请求体中包含要上传的文件数据。具体来说,可以这样构造:

  1. 请求方法为POST
  2. 请求URL为文件上传的接口地址,如https://example.com/uploadopen in new window
  3. 请求头部包含:
  • Content-Type: 表示文件类型,如 image/jpeg、application/pdf 等
  • Content-Length: 表示文件大小
  1. 请求体中包含文件二进制数据

例如:

POST /upload HTTP/1.1
Host: example.com
Content-Type: image/jpeg 
Content-Length: [文件大小]

[文件二进制数据]

其中 Content-Type 表示上传文件的 MIME 类型,Content-Length 表示文件的大小。

请求体中直接包含上传的文件数据,这属于原始的文件上传方式。也可以将文件数据放到表单字段中上传,这种情况下 Content-Type 为 multipart/form-data。

总之,构造文件上传的HTTP请求时,关键是请求体中包含文件二进制数据,并设置好 Content-Type 和 Content-Length 就可以了。

在Java中实现文件上传,可以使用以下代码构造HTTP请求:

// 上传的文件路径 
String filePath = "/path/to/file.jpg";

// 创建请求体,包含文件数据
File file = new File(filePath);
byte[] fileData = FileUtils.readFileToByteArray(file);

// 构造请求参数
HttpPost uploadFile = new HttpPost("http://example.com/upload"); 
uploadFile.setHeader("Content-Type", "image/jpeg");
uploadFile.setHeader("Content-Length", String.valueOf(fileData.length));

// 添加文件数据到请求体
ByteArrayEntity reqEntity = new ByteArrayEntity(fileData);
uploadFile.setEntity(reqEntity);

// 发送请求
HttpResponse response = HttpClientBuilder.create().build().execute(uploadFile);

主要步骤为:

  1. 获取上传文件的数据字节数组
  2. 创建 HttpPost 请求,设置请求头部信息,包括 Content-Type 和 Content-Length
  3. 将文件字节数组放到 ByteArrayEntity 中,添加到请求体
  4. 执行请求,上传文件

通过这种方式,就可以通过Java代码构造上传文件的HTTP请求了。

2.在下载文件时如何限制下载速率?(科大讯飞)

在Java中下载文件时,可以通过限制读取文件的字节数来限制下载速率,主要步骤是:

  1. 使用URLConnection连接远程文件地址,获取InputStream
  2. 创建BufferedInputStream包装InputStream
  3. 读取文件时,每次只读取指定长度的字节数据
  4. 休眠一定的时间再读取下一个区块数据
  5. 重复第3、4步,直到读取完成

示例代码:

URL url = new URL("http://example.com/file.zip");

//连接远程资源
URLConnection conn = url.openConnection(); 

//获取输入流  
InputStream inStream = conn.getInputStream();

//创建缓冲输入流
BufferedInputStream bis = new BufferedInputStream(inStream);

//设置单次读取字节数 
int byteSize = 1024; 

//读取文件
byte[] buf = new byte[byteSize];
int len = 0;
while ((len = bis.read(buf, 0, byteSize)) != -1) {

  //写入文件或进行其他处理
    
  //休眠500毫秒
  Thread.sleep(500); 
}

bis.close();
inStream.close();

通过调整每次读取的字节数byteSize和休眠时间,可以控制下载的速度。

这样通过控制读取文件数据的方式,可以实现限制Java中文件下载的速率。

3.线上项目OOM了怎么定位?(科大讯飞)

3.1. OOM问题产生的原因

所以在运行时能产生OOM问题的基本就是堆和栈了,其中在实际运行中最常见的就是堆内存溢出,我们这次产生的问题也是堆内存溢出导致,堆内存溢出原因主要是以下几种: (1)创建了一个超大对象,比较常见的是一个大数组,大集合 (2)对象引用没有释放,导致垃圾无法回收,产生内存泄漏,从而导致可用内存减少 (3)突然而来的高并发,导致流量飙升,资源占用迅速提升,服务器配置无法跟上实际使用 (4)重写finalize引发频繁GC,这个问题的典型案例是小米云的C++程序员重写finalize导致了线上OOM。在java里很少见

栈存储的是基础数据类型和堆对象的引用,这些数据理论上占用并不多,要达到内存溢出,那就是不断的叠加导致的这些数据暴增,《深入理解JAVA虚拟机》中给出的栈溢出的原因是线程请求的栈深度大于虚拟机允许的深度了,所谓的栈深度就是方法嵌套调用的次数,所以说的直白点就是嵌套循环调用次数太多,即考虑如下原因: (1)是否有递归调用 (2)是否有大量循环或死循环

如何定位是堆内存溢出还是栈内存溢出?

在java中出现堆内存溢出,可以在报错异常java.lang.OutOfMemoryError后明显看到"java heap space"的提示,即堆空间;而栈溢出可以看到StackOverflowError错误,一般这类错误在我们开发测试时就能暴露出来,所以通过报错内容的文字描述即可知道溢出位置。

3.2. 解决OOM问题思路

针对于栈内存溢出:一般通过日志找到报错的代码位置,针对性排查是否有循环调用、死循环的问题即可,针对堆内存溢出: (1)如果是调用量激增导致的内存不足,那么考虑增加机器或拓展内存资源 (2)机器资源充足,只是JVM分配的内存较小,考虑调大JVM内存,通过参数最大内存-Xmx 和最小内存-Xms (2)如果是超大对象,那么考虑业务场景,是否需要查询如此大的对象,考虑分步查出 (3)如果是对象引用没有释放,就排查代码逻辑,查看是否有没有正常释放对象引用的地方,比如ThreadLocal没有正常remove,导致对象一致引用。

3.3. 定位OOM报错

  • 定位到服务器后,到对应服务器上通过top指令,查看是哪个进程占用内存资源较高
  • 然后通过top -Hp 进程pid指令来查看是哪个线程占用的内存资源高
  • 通过jmap指令导出堆日志
  • 将dump文件下载到本地,利用各类工具进行分析
  • 通过mat工具分析(推荐)

线上项目发生OOM(OutOfMemory)异常,可以从以下几个方面来定位:

  1. 查看OOM发生时JVM的堆内存使用情况,通过jmap、jstat等工具dump出堆转储快照,并使用MAT(Memory Analyzer Tool)等工具分析,看是哪部分对象使用内存过高。
  2. 检查代码中是否存在内存泄漏 - 分析堆转储快照找出suspicious的大对象,并跟踪它们的引用链,判断是否存在应用逻辑错误导致的内存泄漏。
  3. 分析GC(垃圾回收)日志,看Old Gen使用是否过高、GC效率是否低下等,可以判断是否存在不合理的对象分配或GC优化不足的情况。
  4. 使用 PROFILE 工具分析代码的内存分配情况,找出分配内存过多的代码热点。
  5. 检查线上是否存在代码或配置变更,导致应用对内存使用量增加。
  6. 检查虚拟机的内存参数设置是否合理,例如堆大小是否设置过小等。
  7. 排查是否存在其他组件比如数据库连接、缓存等内存消耗过高的情况。

参考:https://blog.csdn.net/qq_24950043/article/details/127174391?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-4-127174391-blog-77479344.235^v38^pc_relevant_yljh&spm=1001.2101.3001.4242.3&utm_relevant_index=7open in new window

4.线上项目卡顿有延迟如何找到哪地方的异常?

线上项目出现卡顿或者延迟,可以通过以下思路和手段找到问题所在:

  1. 监控系统指标,比如CPU、内存、IO等,看是否有明显异常。这可以快速定位系统瓶颈所在。
  2. 通过打点统计接口响应时间变化,确定是否某条请求处理变慢。
  3. 对卡顿时间段dump线程栈,分析线程都在做什么,是否存在死锁或等待资源等。
  4. 对数据库SQL进行打点统计,找出慢SQL。

详情:https://www.youngxy.top/page/dev-tools/Linux.html#_6-1java-进程cpu飙升问题open in new window

参考:https://guoguocai.github.io/2022/09/12/JVM-常见线上问题的排查方法/open in new window