1. 如何定位线上OOM
- 造成OOM的原因

- 如何快速定位OOM
![图片[1]-java常见面试场景题 - 拾光赋-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/thumbnail-lg.svg)

2. 如何防止重复下单

方案一:
前端提交订单按钮置灰
用户点击下单按钮后置灰,防止用户无意点击多次

方案二:
后端Redis setnx
用户token +商品URL+ KEY 用setnx 命令并设置过期时间3-5秒防止重复下单

3. 如何设计分布式日志存储架构
单体项目 使用Logback, Log4j记录日志到文件中

- 优点:部署简单,成本低,容易维护,性能高,稳定
- 缺点:在分布式环境下不方便排查问题

如何实现日志分布式系统

MongoDB存储
![图片[2]-java常见面试场景题 - 拾光赋-拾光赋](https://i0.wp.com/i-blog.csdnimg.cn/direct/ed4a7677ed45401fb8b949e0b09c05ac.png)
2. ELK存储

4. 给你以一亿个 Redis keys, 统计双方共同好友
要统计双方共同好友,可以使用Redis的Set数据结构来实现。以下是一个详细的步骤:
-
使用Redis的Set数据结构 :
将每个用户的好友列表存储在一个Set中。例如,用户userid:20002和userid:20003的好友列表可以分别存储在两个Set中。 -
使用交集命令
SINTERSTORE:
Redis自带的交集命令SINTERSTORE可以用来求两个Set的交集,并将结果存储在一个新的Set中。示例命令如下:
SINTERSTORE userid:new userid:20002 userid:20003
这条命令会计算userid:20002和userid:20003的好友列表的交集,并将结果存储在Set userid中。
- 处理大量数据:
存储一亿个数据在Redis中成本可能过高,因为Redis是内存数据库,存储大量数据会占用大量内存资源。因此,需要考虑其他解决方案。
一个更适合的解决方案是使用MySQL作为主要存储,通过分库分表策略分散数据存储压力,并使用缓存(如Redis)存储热点数据,以提高查询效率。
- 使用其他数据库:
如果社交数据非常复杂,可以考虑使用图数据库(如Neo4J)来存储和查询好友关系。Neo4J能够通过命令直接查询可能认识的好友、共同好友等关系。
- 监控和优化:
对于如此大量的数据,需要持续监控Redis的性能和内存使用情况,确保系统的稳定性和高效性。
可以通过使用Redis的INFO命令来获取服务器的状态信息,如内存使用情况、连接数、命中率等,以便及时进行调优。
请注意,处理如此大量的数据需要谨慎设计系统架构,并考虑到数据的扩展性、安全性和性能。在实际操作中,可能还需要根据具体情况进行进一步的优化和调整。
另外,由于Redis是内存数据库,对于存储大量数据(如一亿个key)的情况,需要确保有足够的物理内存来支持,并考虑使用持久化机制(如RDB和AOF)来防止数据丢失。

5. 如何使用redis记录上亿用户连续登录天数
![图片[3]-java常见面试场景题 - 拾光赋-拾光赋](https://i0.wp.com/i-blog.csdnimg.cn/direct/eb069165ac5d4eb095b9ef96c03a7dc5.png)
可以采用Redis提供的Bitmap(位图)数据结构来实现。Bitmap实际上是由一个一个的bit的二进制位所组成的数组,每一个位只能存0和1,非常适合用于这种二值统计的场景。
以下是使用Redis记录上亿用户连续登录天数的基本步骤:
选择数据结构:
使用Redis的Bitmap数据结构。Redis的Bitmap是由string类型所实现的,string类型最大可以存储512兆字节,换算成bit位,可以存储42亿多个bit位,因此足够存储上亿用户的连续登录状态。
设计key和offset:
使用日期作为Bitmap的key,例如“20250209”表示2025年2月9日。
将用户的ID映射到Bitmap的偏移位(offset)。假设用户ID是唯一的数字,那么可以直接将用户ID作为偏移量。例如,用户ID为5的登录状态就存储在offset为5的位上。

记录登录状态:
当用户登录时,使用Redis的SETBIT命令将对应日期和用户ID的位设置为1。例如,如果今天是2025年2月9日,用户ID为5的用户登录了,那么执行命令SETBIT 20250209 5 1。
统计连续登录天数:
要统计某个用户的连续登录天数,可以从当天开始,往前面的日期去推算。例如,要统计用户ID为5的连续登录天数,从当天开始,逐日检查对应位是否为1,直到遇到0为止。
使用Redis的GETBIT命令获取指定位置的bit值。
设置key的过期时间:
一般不需要统计超过30天的连续登录天数,因此可以将当前key的过期时间设置为30天,超过30天自动过期,这样可以有效利用Redis的内存。
6. 查询200条数据耗时200毫秒,怎么在500毫秒内查询1000条数据
如果查询可以并行化(即多个查询可以同时进行而不会相互影响),你可以考虑使用多线程或异步编程来同时发起多个查询。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MultiThreadedQuery {
// 假设这是你的查询方法,单线程查询200条数据耗时200毫秒
private static List<Data> queryData(int start, int count) {
// 模拟查询操作
try {
Thread.sleep(count / 2); // 假设每条数据查询耗时0.5毫秒(仅为示例)
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
List<Data> dataList = new ArrayList<>();
for (int i = 0; i < count; i++) {
dataList.add(new Data(start + i)); // 添加模拟数据
}
return dataList;
}
// 使用多线程查询数据
public static List<Data> queryDataInParallel(int totalCount, int threadCount, int batchSize) {
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
List<Future<List<Data>>> futures = new ArrayList<>();
for (int i = 0; i < totalCount; i += batchSize) {
final int start = i;
final int count = Math.min(batchSize, totalCount - i);
futures.add(executor.submit(new Callable<List<Data>>() {
@Override
public List<Data> call() throws Exception {
return queryData(start, count);
}
}));
}
List<Data> result = new ArrayList<>();
for (Future<List<Data>> future : futures) {
try {
result.addAll(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
return result;
}
public static void main(String[] args) {
int totalCount = 1000;
int threadCount = 5; // 假设使用5个线程
int batchSize = 200; // 每个线程查询的批次大小
long startTime = System.currentTimeMillis();
List<Data> data = queryDataInParallel(totalCount, threadCount, batchSize);
long endTime = System.currentTimeMillis();
System.out.println("Queried " + data.size() + " items in " + (endTime - startTime) + " ms.");
}
// 假设的数据类
static class Data {
private int id;
public Data(int id) {
this.id = id;
}
// getter, setter, toString等方法可以根据需要添加
}
}
7. 怎么用java实现一个简单的消息队列

public class SharedQueue {
//声明队列最大长度
private int queueSize = 10;
private ArrayBlockingQueue<Integer> queue= new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
SharedQueue sharedQueue = new SharedQueue();
//消费者持续运行
Consumer consumer = sharedQueue.new Consumer();
consumer.start();
//生产10条消息
for (int i = 1; i <= 10; i++) {
//创建10个生产者线程
Producer producer = sharedQueue.new Producer();
producer.start();
}
}
//生产者
class Producer extends Thread{
@Override
public void run() {
//保证生产者在整个过程中是安全的
synchronized (queue){
//1.判断当前队列长度是否小于最大长度
if(queue.size()<queueSize){
//2.如果小于,生产者就可以生产消息了
//2.1 往队列添加一条消息
queue.add(queue.size()+1);
System.out.println("生产者往队列中加入消息,队列当前长度:"+queue.size());
//唤醒消费者有活了
queue.notify();
try {
//模拟业务处理
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//3.如果大于,生产者停止工作,稍微歇一歇
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Consumer extends Thread{
@Override
public void run() {
//消费者需要重复的工作
while(true){
//保证整个消费过程是线程安全的
synchronized (queue){
//如果队列为空,睡眠
if(queue.isEmpty()){
System.out.println("当前队列为空....");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
//如果出现异常,手动唤醒
queue.notify();
}
}else{
//队列不为空,消费者进行消费
//消费头部信息
Integer value = queue.poll();
System.out.println("消费者从队列中消费了信息"+value+",队列当前长度:"+queue.size());
//消费完消息,唤醒生产者可以继续生产了
queue.notify();
try {
//模拟业务处理
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
8. redis如何实现上亿用户实时积分排行榜
![图片[4]-java常见面试场景题 - 拾光赋-拾光赋](https://i0.wp.com/i-blog.csdnimg.cn/direct/4021ba7dfd294902a3b5043844c25274.png)
实现上亿用户实时积分排行榜是一个复杂且高性能要求的任务,但Redis凭借其出色的数据结构和性能,非常适合处理这种场景。以下是一个基于Redis的实现思路:
数据结构设计
使用Sorted Set(有序集合)
Redis的Sorted Set(ZSET)是带有权重的集合,其内部实现是跳表加字典,能够高效地支持按权重(这里是积分)的排序和范围查询。因此,我们可以使用Sorted Set来存储用户的积分信息。
- Key:可以是一个固定的字符串,比如”user_leaderboard”。
- Member:用户的唯一标识,比如用户ID。
- Score:用户的积分。
数据操作
添加或更新用户积分
当用户获得或失去积分时,需要更新排行榜。这可以通过Redis的ZADD命令来实现,该命令可以添加或更新集合中成员的分数。
ZADD user_leaderboard score member
例如,给用户ID为123的用户增加100积分:
ZADD user_leaderboard 100 123
如果用户已经存在,则ZADD会更新其积分。
查询排行榜
要获取积分排行榜,可以使用ZRANGE或ZREVRANGE命令。这两个命令分别按升序和降序返回集合中的元素。
# 获取积分前100名的用户
ZREVRANGE user_leaderboard 0 99 WITHSCORES
获取用户排名
要获取特定用户在排行榜中的排名,可以使用ZREVRANK命令。
# 获取用户ID为123的用户在排行榜中的排名(从0开始)
ZREVRANK user_leaderboard 123
优化方案:分桶而治
一个key存储上亿用户太大,根据key来进行区分,1000以上的存到一个桶里

9. 内存200M读取1G文件并统计重复内容
分块读取并放到hashMap中
- 我们使用BufferedReader以流的方式逐块读取文件,每次读取一个字符数组(缓冲区)。
- 当我们遇到换行符时,我们认为这是一行的结束,并处理这一行(在这里,我们简单地统计了每一行的出现次数)。
- 我们使用一个HashMap来存储每行内容及其出现的次数。
- 最后,我们遍历HashMap并打印出出现次数大于1的内容。
这种方法的关键在于它不需要一次性将整个文件加载到内存中,而是逐行(或逐块)处理文件内容,因此非常适合处理大文件。你可以根据需要调整缓冲区的大小(BUFFER_SIZE),以及处理内容的方式(例如,按单词而不是按行统计)

极端情况下内容不重复,那这个时候map就会产生大量的key, 此时可以先将数据写到每个分片文件,
读取每个分片文件,用map存储并统计


总结: 分块读取大文件 --> 文件分片 -->逐个统计
10. Springboot防盗链的几种方式
在Spring Boot中实现防盗链功能,可以通过多种方式来实现,主要包括使用过滤器(Filter)、拦截器(Interceptor)以及配置反向代理服务器(如Nginx)等。以下是几种方式的详细说明:
- 使用过滤器(Filter)
通过创建一个自定义过滤器,可以在请求到达实际资源之前检查HTTP头中的Referer字段。如果Referer不在允许的域名列表中,则返回403 Forbidden响应或重定向到其他页面。

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class HotlinkProtectionFilter implements Filter {
private final String[] allowedDomains = { "yourdomain.com"};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String referer = httpRequest.getHeader("Referer");
if (referer == null || Arrays.stream(allowedDomains).anyMatch(referer::contains)) {
chain.doFilter(request, response);
} else {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, "Hotlinking not allowed");
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void destroy() { }
}
然后,将这个过滤器注册到Spring的上下文中:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<HotlinkProtectionFilter> loggingFilter() {
FilterRegistrationBean<HotlinkProtectionFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new HotlinkProtectionFilter());
registrationBean.addUrlPatterns("/resources/*"); // 替换为你的资源路径
return registrationBean;
}
}
- 配置Nginx等反向代理服务器
在网关层使用Nginx等反向代理服务器进行防盗链配置也是一种常见且简单的方式。通过拦截访问资源的请求,并检查请求头中的Referer地址是否为本站,如果不是则进行阻止或重定向。
server {
listen 80;
server_name www.yourdomain.com;
location / {
root /web;
index index.html;
}
location ~* \.(gif|jpg|png|jpeg)$ {
root /web;
valid_referers none blocked yourdomain.com;
if ($invalid_referer) {
return 403;
}
}
}
11. springboot,springmvc和spring的区别
三者是包含的关系,springboot包含spring, spring 包含springmvc
Spring是全面的企业级开发框架,Spring MVC是Spring的Web MVC模块,Spring Boot是简化Spring应用开发的快速开发框架。
Spring:提供IoC和AOP等核心功能,支持企业级应用开发,涵盖web层、业务层、持久层等12。
Spring MVC:Spring的Web MVC框架,专注于Web应用的MVC架构,解决WEB开发问题,配置相对繁琐12。
Spring Boot:简化Spring应用开发的框架,提供自动配置和默认配置,降低项目搭建复杂度,快速上手开发
12. 如何防止SpringBoot反编译
可以使用一些工具(如ClassFinal-maven-plugin, JBCO)将字节码加密,并在运行时动态解密。这种方法可以有效地保护源代码的安全性,但需要在运行时进行解密操作,可能会影响程序的性能。
- 引入插件
<!-- ClassFinal加密 -->
<plugin>
<groupId>net.roseboy</groupId>
<artifactId>classfinal-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<!-- 可采用机器码的方式绑定机器 需要提前获得机器码写入code -->
<!--<code></code> -->
<!-- 密码为#可以直接用javaagent启动 也可以设置其他密码 dockerFile脚本中需要标注=“-pwd 123456” -->
<password>123456</password>
<excludes>org.spring&l
原文链接:java常见面试场景题


![图片[1]-java常见面试场景题 - 拾光赋-拾光赋](https://i0.wp.com/i-blog.csdnimg.cn/direct/c1adf80195f047e2bc79256b79e2d132.png)

![表情[baoquan]-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/smilies/baoquan.gif)


暂无评论内容