java面试笔记
java场景题
一、百万数据导出Excel怎么解决oom问题。
oom:堆内存溢出
解决方法:多线程分批导出:先查出所有情况,再次分批导出
原因:单线程:从数据库查找到所有数据(1g),封装为一个byte数组通过resultSET传给java内的一个list。
多线程:单个线程从数据库查找到数据(512m),封装为一个byte数组传给java内的一个list,使用完后清空byte数组再次添加数据。
二、 线上定位oom
oom原因:1.一次性申请对象太多(分页,一次查一点)
2.内存资源耗尽未释放(池化思想,最多申请多少对象,释放对象)
3.分配的内存不足(jamp -heap 线程号,查看堆信息)
定位:
1.已oom: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath= 运行前 输出dump文件(插件+运行前vm配置)
使用jprofiler(jvisualvm)查看(使用方法查看我的jvm)
2.未oom:
查看所有对象:jmap -histo:live 进程ID
运行时输出dump文件(会产生一次gc会stw),jmap -dump:format=b,file=test.hprof 进程ID或使用arthas工具
结合jprofiler(jvisualvm)调试,查看有关对象-找到gcroot-查看线程栈。
三、线上cpu飙高的定位解决办法
1.使用top找到cpu占用最高的进程的PID
2.找到其对应的线程。
top -H -p 进程PID
3.将线程PID转为16进制
printf '0x%x\n' 线程PID
查看详细信息
jstack 进程PID | grep 16进制线程PID -A 20
四、定位和避免死锁
死锁:线程在并发下因为相互等待出现的“永久”阻塞的现象。
定位方法:
1.打开命令窗口
2.查看当前程序进程
jps
jstack 进程ID
解决方法:
破坏死锁条件(互斥,占有且等待,不可抢占,循环等待)
1.互斥(原子操作,TreadLocal,乐观锁(CAS))
2.占有且等待(将公共资源共同管理,让一个线程占有)
3.不可抢占(使用lock设置锁住时间)
4.循环等待(资源有序化)、
**避免方法:**银行家算法
五、秒杀系统的设计
1.将秒杀页面静态化(动态的jsp,css,图片等全交给服务端处理压力较大),将静态页面同步到cdn服务器。
2.活动之前禁用秒杀按钮,用户点击之后禁用按钮,添加验证码,用户点击后给予反馈。
3.Nginx负载均衡。
4.使用redis缓存,秒杀前将商品的信息预热到redis中,通过Lua脚本操作库存(判断库存是否充足,数量减一,判断是否第一次),使用分布式锁。
5.使用rocketmq异步下单,进行流量的削峰。
6.读写分离,数据量大的话采用分库分表操作。
六、订单超时如何实现
**1.jdk自带的延时队列(EQR):**简单,不需要其他第三方插件;占用内存大(所有超时时间加入到DelayQueue中),不支持分布式。
**2.rocketMQ延时消息(实现原理):**使用简单,支持分布式,支持任意时刻;定时最大值为24h,存储成本高,每个订单都会有一个定时消息,同一时刻大量消息可能会产生延迟(定时结束后传递给消费者)。
**3.基于redis的过期监听:**简单,支持分布式;(所有中间件的缺点)不可靠,如果过期通知时应用刚好重启可能会产生事件丢失,订单无法关闭,大量订单占用空间大,需要额外成本。
**4.定时任务分布式批处理:**在定时任务中实现一个指定时间的定时任务,批量的调度处理。稳定性强(本次任务未调度,可在下次任务中调度),效率高(批量处理,一次性拿出全部超时任务,批量更新),可运维性高(失败可手动sql进行批量运维),成本低;精度较低。将所有需要处理超时的业务提取出来,抽象一个超时中心,超时库单独进行超时处理,不影响线上数据库。只需考虑幂等性和单批的性能。
**总结:**超时精度较高,超时时间在24小时以内,不会有峰值压力:RocketMQ。
超时场景在24小时以上,超时精度不敏感,海量订单进行批处理:基于定时任务的批处理方法。
七、如何防止重复下单
1.前端用户下单一次后将按钮设置为不可用,
2.后端使用redis的setnx来保证唯一幂等性(setnx:保存时哪个key没有值就返回true,否则返回false),以用户token+请求路径+自定义key作为key存储,要给setnx设置一个3,5秒的过期时间(可以自定义注解)
八、如何防止人肉机刷单
1.绕过前端页面直接请求接口的进行防刷,限流(nginx,MQ,网关等等)
2.在redis通过setnx保证同一用户同一接口只有一次请求
3.在注册时使用手机验证码
4.添加图形验证码
5.同一ip只能一次
九、分布式锁
(redis和zookeeper两种方式)
redis的setnx来实现,一定要设置过期时间(若一个线程挂掉,其他线程还可以使用,避免死锁)
锁过期后业务尚未完成:加长时间,添加一个子线程监视(每10s)该线程是否在线,在线则延期。
处理完释放了其他线程的锁:给锁加唯一id。
Redisson组件可使用lock.lock实现分布式锁。原理:加锁后以UUID+线程id作为key,内部有一个看门狗,保证时效性,获取锁失败则自旋。当我们使用了集群主节点挂掉了,需要使用Redlock将锁在每个节点同步,保证强一致性。
十、扫码登录实现原理。
1.生成二维码:pc请求后端生成二维码,后端生成一个全局唯一的二维码id来保存当前二维码状态,存储在redis中并且将二维码id返回给前端生成图片。可以使用(websocket,sse)轮询,长链接等实现二维码状态检测。
2.扫码:手机登录后携带手机端的token,二维码id请求后端,后端检查token并且将二维码改为已待确认,返回给手机一个临时token。
**3.确认:**手机端确认后携带临时token请求后端,后端将二维码状态改为已确认,轮询检测到后,生成一个pc端登录的token,删掉临时token。
十一、如何设计一个分布式日志存储架构
单体项目:logback,log4f存储在文件中(部署简单,成本低,性能高,稳定)
分布式或微服务项目:生成日志管理系统,通过消息队列异步存储日志信息。
1.MongoDB存储:生成的日志通过MQ异步存储到MongDB(nosql数据库,使用json存储数据,支持分片)中,自定义一个web页面来展示。
2.Elk:elasticsearch(存储日志数据)+kibana(展示日志UI)+logStash(收集日志信息)。其中logStash可以使用FileBeat代替,相对轻量级,在业务服务层实现,在客户端实现日志收集。
3.Loki:Grafana的一个插件,loki用于存储日志信息,使用promtail收集日志信息最后在Grafana展示。
十二、redis击穿雪崩穿透
**击穿:**某一个热点消息在redis中失效,大量数据请求数据库导致。数据设置永不过期(当访问量达到某个阈值设置为永不过期),加锁(同步锁,分布式锁)排队(reids查不到,在数据库层面加个锁,访问后将key添加入redis使用双重检查锁,在查数据库前再次查询redis)。
**雪崩:**缓存集中过期。加锁排队,随机失效时间,reids的集群,哨兵模式。
**穿透:**请求reids和数据库都没有的数据。请求的参数校验,缓存空对象(不管查没查到都同步到reids,设置过期时间),布隆过滤器(数据结构,用户访问布隆过滤器没查到,在访问数据库也没有,下次查询布隆过滤器返回空,或者,设置布隆过滤器有的返回,没有的返回空)。
十三、如何使用redis记录用户连续登录多少天
使用redis的Bitmap(二进制位组成的数组,string实现)实现。
**按天统计(一个月内,大量用户):**日期作为key,将用户id映射到bitmap的偏移位,每天一个bitmap

按用户统计
十四、一个亿的reids keys统计双方共同好友
用户id作为key,好友作为value存储,使用redis的sinterstore求两个人的交集
SINTERSTORE userid:res userid:1 userid:2
将一亿个好友分库分表存放在mysql中,通过join来求交集。做定时器将热点共同好友存入redis。(少量)
Neo4j:支持命令快速查询,做定时器将热点共同好友存入redis。(专门的)
Hbase+Hadoop(通用)
十五、redis上亿用户实时积分排行
zset:key user:top–>value (score 100 value 亚瑟)
通过reverseRangeWithScores排行,数据量较大建议存放在桶里1000以上一放,500以上一放等等。
十六、内存200m读取1g文件并且统计重复次数。
分块读取,将读取的内容放到字符集,循环字符集将每一个字符存储在map的key中,value来记录出现的次数。
如果数据量过大存储在磁盘,进行哈希分片存储。
十七、查询200条数据耗时200ms,怎么在500ms内查询100条数据
多线程异步读取,最后将结果合并。
十八、springboot线程池实现百万数据批量插入
io密集型:较多的文件读取操作。(核心线程数:cpu核心数×2再加1,最大线程数位核心线程数2倍)
cpu密集型:较多的cpu利用,排序等。
动态线程池
十九、springboot同时可以处理多少请求
自定义配置的最大连接数(8192)+最大等待数(100)。
二十、volatile的应用场景
保证并发编程的可见性和有序性
可见性:每个线程有一个自己的工作空间,将主内存的共享变量复制一份到自己的工作空间 ,加上后每当修改这个共享变量会进行同步。
有序性:防止指令重排。
二十一、sql执行过程
执行sql,首先与远程数据库建立连接,用户名密码没问题就会来到mysql的服务层,先查缓存(key(sql语句),value(数据)的形式存储)再mysql8完全弃用,进入解析器解析sql语句语法是否正确,然后进入预处理器检查表,字段等是否存在,再进入优化器优化sql(优化为最左前缀法),最后操作存储引擎,返回结果。
二十二、单表多少数据算大
b+数小于4层,推荐500万行或单表容量超过5G(阿里)。
二十三、mysql中b树与b+树有什么区别
b树每一个节点都存储了数据和索引,b+树数据在叶子节点,其他节点只存储索引。b+树通过双线链表连接每一个叶子节点,范围查询性能更好。
二十四、mysql引擎层是如何工作的
mysql的两大内存BufferPool,RedoLogBuffer和三大日志binlog,redolog,undlog
客户端调用执行器,执行器调用InnoDB引擎,查看BufferPool缓存中是否存在该数据,有则更新,没有则从磁盘(.idb)加载该数据,将一整页数据加载到BufferPool中,然后进行修改,将旧数据写入Undlog用于数据回滚,最后将BufferPool中的数据同步到磁盘。
Redolog:在bufferPool修改后新数据保存到Redolog中,可以保证数据不丢失。redolog顺序写方便数据恢复,所以不先写磁盘。属于InnoDB引擎。
RedoLogBuffer:提高写入Redolog的效率。修改数据后写如RedoLogBuffer,再同步,0:延迟写,延迟刷;每隔一秒写入;1:实时写,实时刷;提交时写入;2:延迟写,延迟刷;写入系统缓存。
binlog属于mysql
二十五、聚簇索引与非聚簇索引的区别
聚簇索引:数据和索引存储在一起(主键索引)InnoDB数据存储再.db
非聚簇索引:数据和索引分离(非主键索引),性能差,查非索引数据需要回表。MyISAM:索引在.MYI,数据在.MYD
二十六、count(*)count(1)count(字段)
count(*):系统优化为count(0)
count(1):
count(字段):排除数据为null的
二十七、sql语句前模糊索引优化
反向索引:将索引数据存储一份反向的(xy–》yx)
限制范围:限制为当天的数据,当月数据等
索引覆盖:
ES
二十八、分库分表后ID解决办法
1.设置步长:第一个表起始值为1,步长为2,第二个表起始值为2,步长为2;
2.数据库号段:额外的号段表,使用号段表里的id
3.UUID:降低数据插入性能,可以保留原始主键id添加一个uuid字段
4.redis生成id(jedis.incr(key))
5.雪花ID:组成:41bit的时间戳(69.73年),10bit的工作进程位,12bit的序列号位。
二十九、深分页优化
原因:从第0条数据开始查,一直查到你想要的数据。
优化:字段索引覆盖,通过id来限定范围(where id>10000),通过id分库分表,ES大数据等。限制可查询范围。
三十、MVCC(多版本并发控制器)
事务隔离级别的无锁实现方式,提高事务的并发性能。
事务隔离级别:解决多个事务并发产生的问题。
读未提交:会产生脏读:事务1修改某个值,事务2读取的是事务1修改的值,但事务1失败执行回滚。
读已提交:产生不可重复读问题:
=幻影读:
串行化:
MVCC:隐藏列(DB_TRX_ID:当前数据由哪个事务id维护的(持有),DB_ROLL_PTR:当前数据修改前的数据的指针,维护主键id)
三十一、
更多推荐
所有评论(0)