系列博客目录
1.流程
-
自我介绍
-
项目难点:在项目中,数据报表导出时遇到了性能瓶颈,主要是在导出大数据量时,使用 Apache POI导出的方式导致 CPU 占用过高 和 内存溢出(OOM) 的问题,甚至出现了程序假死的现象。为了优化这个问题,我进行了以下改进:首先,我将 Apache POI替换为EasyExcel,因为 EasyExcel 支持流式操作,可以逐行写入,避免了将所有数据加载到内存中,从而有效降低了内存消耗并解决了 OOM 问题。其次,我引入了 多线程,将数据导出任务分成多个线程并行处理,每个线程处理不同数据块,从而利用多核 CPU 提升了导出效率。最终,我使用了 JMeter和 JVisualVM进行性能测试,优化后,导出速度提升了 30%,并且 内存占用减少了 40%,系统性能大幅提高,避免了导出大数据时的卡顿和高负载问题。这一优化措施不仅提升了数据导出的效率,还提高了系统的稳定性
-
项目难点还没说完,就被打断被问到 多线程怎么做到减少内存的使用。应该是前面口误,把“我引入了 多线程,将数据导出任务分成多个线程并行处理,每个线程处理不同数据块,从而利用多核 CPU 提升了导出效率”说成了多线程可以减少内存使用了。
-
多线程在导出文件的过程中有没有起到优化的过程。
-
问到项目还有没有什么难点,我又说了:“使用Redis 和 Caffeine 实现多级缓存,目的是结合二者的优势来提高系统性能。首先,当用户请求数据时,系统会先检查本地缓存(Caffeine),如果命中则直接返回。如果本地缓存未命中,则查询 Redis,如果 Redis 中也没有数据,再从数据库中查询。这样,频繁访问的数据可以保存在本地缓存中,减少 Redis 的负载,提高查询效率。通过这种方式,系统能够在保证高性能的同时,减轻 Redis 和数据库的压力,提升整体的响应速度。”
-
又问了一下项目还有什么难点或者亮点,我说没了。。。准备不够充分
-
redis和本地缓存如何保持一致性?要保持本地缓存和Redis一致,可以采用以下几种常见方法:
缓存更新策略:
• 定时更新:设置一个固定的时间间隔,定期从Redis中获取最新数据来更新本地缓存。这种方式实现简单,但可能存在数据更新不及时的问题。
• 事件驱动更新:当Redis中的数据发生变化时,通过消息队列等方式发送通知,本地缓存接收到通知后立即进行更新。这样能保证数据的实时一致性,但需要额外的消息机制支持。
数据同步机制 -
自己在编码是可不可以创建一个com.byteDance.string类,在想要使用它的时候能不能拿到它。我回答不可以因为,我如果编写,可能会与大佬们已经创建好的类冲突。他又提问先不说这个问题,为什么有些包中的类他有很多相同的名字,我们是怎么知道调用哪个的呢,比如Jackson有很多包中的类名都叫Jackson
-
Redis中ZSet你知道吗
-
底层用的跳表,为什么用跳表 不用红黑树
-
跳表是什么
-
volatile关键字可以保证线程安全吗
-
SPI
-
在使用Spring是,interceptor和filter的区别是什么? 回答的时候提到了MVC
-
SpringMVC的执行流程是什么
-
查找数据库中的一行数据很慢,是为什么
-
在进行DML和DDL操作的时候,会影响上面的查询,导致他慢吗
-
Mysql我想存IP地址,应该如何存?在MySQL中存储IP地址,通常有以下两种方式:
使用字符串类型(VARCHAR)
• 优点:直观,易于理解和使用,可直接存储IP地址的字符串形式,如’192.168.1.1’,对IP地址的格式没有严格限制,存储和查询操作相对简单。
使用整数类型(INT)
• 优点:占用空间小,存储和查询效率高,可通过将IP地址转换为整数进行存储和计算。
-
为什么要进行转换(上面提到的)?将IP地址转换为整数存储主要有以下原因:
• 节省存储空间:IPv4地址用点分十进制表示,如192.168.1.1,需15个字符存储。而转换为32位整数,只需4个字节,能节省存储空间,在存储大量IP地址时优势明显。IPv6地址用VARBINARY(16)存储,相比字符串形式也更省空间。
• 提高查询效率:整数在数据库中比较和排序的效率比字符串高。以整数形式存储IP地址,在进行范围查询、比较操作时,数据库可直接对整数进行计算和比较,无需像处理字符串那样逐个字符比较,能显著提高查询性能。
• 便于计算:将IP地址转换为整数后,便于进行一些与IP地址相关的计算,如判断IP地址是否在某个网段内等。通过对整数进行位运算,可以快速实现这些功能,提高数据处理的效率。
-
大key如何删除
在Redis中,删除大key(指存储大量数据的key)是一个需要谨慎处理的问题,因为直接删除这些大key可能会导致阻塞,影响Redis的性能。以下是一些常见的策略和方法,用于删除Redis中的大key:
1. 使用 UNLINK
命令
UNLINK
命令是删除大key的推荐方式。与DEL
命令不同,UNLINK
是异步执行的,它会将删除操作移到后台执行,避免阻塞Redis主线程。这样,删除大key时不会影响到其他命令的执行。- 优点:减少阻塞,删除过程异步进行。
- 示例:
UNLINK large_key
2. 使用 DEL
命令(需要谨慎)
DEL
命令是同步删除命令,当删除一个大key时,Redis会阻塞直到该操作完成。删除大key时,这个过程可能会持续几秒或更长时间,导致系统性能下降,甚至出现“请求超时”或“服务不可用”的问题。- 优点:删除简单。
- 缺点:可能会导致长时间的阻塞,影响系统的响应速度。
如果必须使用 DEL
命令删除大key,可以选择在低流量的时段进行,或者采用其他优化方法,如分片删除(见下文)。
3. 分步删除(渐进式删除)
- 对于大集合(如大列表、哈希表或集合),可以考虑逐步删除集合中的元素,而不是一次性删除整个大key。使用 Redis 的
SCAN
命令可以遍历大key的所有元素,然后分批次地删除。 - 示例(针对大集合):
这里使用SCAN 0 MATCH large_key:* COUNT 1000
SCAN
命令来扫描并逐步删除集合中的元素,避免一次性删除时带来的阻塞。
- Redis中Zset
- Zset为什么使用跳表
- 跳表是什么
- static和final属性都是什么时候赋值
- 多线程循环打印123
题目描述:启动3个线程,线程1无限循环打印1、线程2无限循环打印2、线程3天限循环打印3,要求按123123.顺序循环打印
public class OrderedPrinting {
// 共享锁对象
private static final Object lock = new Object();
// 当前应该打印的数字
private static int currentNumber = 1;
public static void main(String[] args) {
// 创建并启动三个线程
Thread thread1 = new Thread(new Printer(1));
Thread thread2 = new Thread(new Printer(2));
Thread thread3 = new Thread(new Printer(3));
thread1.start();
thread2.start();
thread3.start();
}
static class Printer implements Runnable {
private final int number;
public Printer(int number) {
this.number = number;
}
@Override
public void run() {
while (true) {
synchronized (lock) {
// 如果当前线程不是应该打印的线程,则等待
while (number != currentNumber) {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// 打印数字
System.out.print(number);
// 更新下一个应该打印的数字
currentNumber = currentNumber % 3 + 1;
// 唤醒所有等待的线程
lock.notifyAll();
}
}
}
}
}