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)

三十一、

更多推荐