💯 铜三铁四,我收集整理了这些大厂面试场景题 (一)

面试导航 是一个专注于前、后端技术学习和面试准备的 免费 学习平台,提供系统化的技术栈学习,深入讲解每个知识点的核心原理,帮助开发者构建全面的技术体系。平台还收录了大量真实的校招与社招面经,帮助你快速掌握面试技巧,提升求职竞争力。如果你想加入我们的交流群,欢迎通过微信联系:yunmz777

并发请求来不及写数据库怎么办

在并发请求时,如果写数据库操作来不及执行,可以考虑以下几种解决方案:

1. 使用队列(Queue)

将所有的数据库写入操作放入队列中,按顺序依次处理,避免数据库操作被并发请求压垮。

解决方案:
  • 将写入数据库的请求加入到队列中。

  • 每次从队列中取出一个请求,执行数据库操作。

  • 使用 异步任务队列系统 (如 RabbitMQRedis Queue 等)来确保数据库操作按顺序执行。

2. 批量处理

将多个数据库操作合并成一个批量操作进行写入,减少数据库写入次数,从而提升性能。

解决方案:
  • 将多个并发请求的数据收集到一起,在适当的时机批量插入数据库。

  • 可以使用 事务 来保证批量写入的原子性。

3. 数据库写入优化

通过优化数据库的写入性能,减少每次写入的延迟。可以使用以下方法:

  • 数据库索引优化:避免频繁更新索引,提高写入速度。

  • 异步写入:如果数据库操作不要求立即生效,可以将写入操作异步化,允许系统继续处理其他请求。

  • 缓存写入:可以将数据缓存到内存(如 Redis),然后定期或在后台批量写入数据库。

4. 分布式锁

如果并发请求对同一资源进行修改,可以使用 分布式锁 (如 RedLock)来确保只有一个请求在某个时刻能写入数据库,避免竞态条件。

解决方案:
  • 在操作数据库时,使用分布式锁,保证每次只有一个请求能够访问数据库写入操作。

总结:

  • 使用 队列批量处理 解决并发写入问题。

  • 通过 数据库写入优化异步操作 提升性能。

  • 如果涉及同一资源,可以使用 分布式锁 来确保写操作不会冲突。

客户跟你抱怨网页太慢,你怎么用客户电脑定位问题?

当客户抱怨网页太慢时,你需要用客户的电脑进行排查,找出具体的性能瓶颈。你可以按照以下步骤进行分析:

1. 了解具体问题

首先,和客户沟通,获取更多细节:

  • 网页是整体慢,还是特定页面或功能慢?

  • 是在所有设备和网络环境下都慢,还是只在客户的电脑上慢?

  • 是首次加载慢,还是每次都慢?

  • 具体慢在哪里?是页面打开慢、加载资源慢、交互延迟,还是某些按钮点击无响应?

这些信息能帮助你缩小排查范围。

2. 用开发者工具(F12)进行性能分析

在客户的浏览器(Chrome、Edge、Firefox)中打开 开发者工具 (按 F12Ctrl + Shift + I),然后检查以下几个关键点:

(1)网络(Network)面板

  1. 让客户刷新页面(Ctrl + RF5)。

  2. 查看 "DOMContentLoaded""Load" 时间:

    • DOMContentLoaded 代表 HTML 和同步 JavaScript 解析完成。

    • Load 代表所有资源(图片、CSS、JS 等)加载完成。

    • 如果 "DOMContentLoaded" 非常慢,说明 HTML 或者 JavaScript 解析有问题。

    • 如果 "Load" 时间很长,可能是资源加载过多、CDN 问题、服务器响应慢等。

  3. 检查加载资源

    • 找出 加载时间最长的资源,可能是:

      • 某个图片/视频太大,导致加载过慢。

      • 某个 API 请求慢,导致数据获取延迟。

      • 某个 JS 或 CSS 资源加载太久,影响渲染。

  4. 检查是否有失败的请求

    • 404、500、504 错误可能影响页面加载速度。

(2)性能(Performance)面板

  1. 录制页面加载情况

    • 点击 Record,然后刷新页面。

    • 查看 主线程(Main Thread) 是否有长时间的任务阻塞页面交互。

  2. 检查渲染阻塞

    • 过多的 JavaScript 执行可能会导致渲染卡顿。

(3)JavaScript Profiler

  • Performance 里检查 脚本执行是否占用过多时间

  • 过多的 JS 可能会导致浏览器 主线程阻塞,影响页面交互流畅度。

3. 运行 Chrome Lighthouse 进行优化分析

  1. 开发者工具 -> Lighthouse 选项卡中,选择 Performance 进行评测。

  2. 分析得分,看看是否有:

    • 服务器响应时间过长(TTFB 高)。

    • 图片优化不足(未使用 WebP)。

    • 过大的 JS 或 CSS 资源(未压缩)。

    • 阻塞渲染的资源(JS、CSS 影响首屏渲染)。


4. 检查客户电脑和网络

(1)检查网络速度

  • 让客户 测试网络 (如 speedtest.net),检查:
    • 带宽是否够快?
    • 延迟是否过高?
    • 是否是 WiFi 连接问题?
    • 是否有其他设备占用大量带宽?

(2)检查 DNS 解析

  • 让客户 ping 网站:

    sh 复制代码
    ping example.com

    如果延迟高,可能是 DNS 解析慢 ,建议换 Google DNS(8.8.8.8)Cloudflare DNS(1.1.1.1)

  • 也可以 nslookuptracert 进行进一步排查:

    sh 复制代码
    nslookup example.com
    tracert example.com

(3)检查 CPU、内存、磁盘

  • 如果客户电脑很慢 ,可能是 CPU 或 内存使用率过高:
    1. 打开 任务管理器(Ctrl + Shift + Esc)
    2. 查看 CPU、内存、磁盘占用,是否有异常进程。
    3. 关闭不必要的后台程序。

5. 服务器端排查

如果本地检查没问题,可能是服务器问题:

  1. 服务器响应时间(TTFB)是否过高?
  2. CDN 访问是否正常?
  3. API 响应时间是否太长?
  4. 数据库查询是否耗时过长?

可以让客户在终端运行:

sh 复制代码
curl -o /dev/null -s -w "%{time_total}\n" https://example.com

看看总响应时间是否过长。


总结

如果客户电脑网页加载慢,应该按照以下步骤排查:

  1. 和客户确认具体问题(网页整体慢?交互慢?资源加载慢?)

  2. 使用 F12 开发者工具分析

    • Network 面板检查 资源加载
    • Performance 面板检查 渲染阻塞
    • Lighthouse 评测优化建议
  3. 检查客户网络

    • speedtest 进行网络测速
    • ping 检测延迟
    • nslookuptracert 追踪域名解析
  4. 检查客户电脑

    • 任务管理器查看 CPU、内存、磁盘占用
  5. 服务器端检查

    • 服务器响应是否正常

    • API 响应时间是否过长

    • 是否有数据库性能问题

通过这个流程,能有效定位问题并给客户提供优化方案!

控制请求并发的数量,例如有8个请求需要发送,如何控制同时只能并行发送两个请求

控制请求并发数量,确保一次只能并行发送指定数量的请求(例如同时只并行发送两个请求),可以使用多种方法。这里有几种常见的解决方案:

1. 使用 Promise 和 async/await 控制并发

我们可以使用 async/awaitPromise 来控制并发请求的数量,使用 for 循环map 来逐步发送请求,并确保在每次请求完成后再开始下一个请求。

示例代码:
javascript 复制代码
async function sendRequests(requests, maxConcurrency) {
  let index = 0;

  // 控制并发请求的函数
  async function handleRequest() {
    if (index >= requests.length) return;

    const currentIndex = index++; // 获取当前请求索引
    await requests[currentIndex](); // 等待当前请求完成
    handleRequest(); // 发起下一个请求
  }

  // 初始化最大并发请求数量
  const concurrencyPromises = Array.from(
    { length: maxConcurrency },
    handleRequest
  );

  await Promise.all(concurrencyPromises); // 等待所有并发请求完成
}

// 示例:模拟 8 个请求,最多并发 2 个
const requests = Array.from(
  { length: 8 },
  (_, i) => () =>
    new Promise((resolve) => {
      console.log(`Request ${i + 1} started`);
      setTimeout(() => {
        console.log(`Request ${i + 1} finished`);
        resolve();
      }, Math.random() * 2000); // 随机延时模拟请求
    })
);

sendRequests(requests, 2);
解释:
  • requests 是一个包含 8 个请求的数组,每个请求用一个返回 Promise 的函数表示。
  • maxConcurrency 控制并发请求的数量(本例中是 2)。
  • 使用 handleRequest 函数逐个处理请求,确保同一时间内只会有最多 2 个请求在并发执行。

2. 使用队列控制并发

通过使用一个 队列 来控制请求的发送。每次发送请求时,从队列中取出一个任务并执行,同时控制并发数量。

示例代码:
javascript 复制代码
function controlConcurrency(requests, maxConcurrency) {
  const queue = [...requests]; // 请求队列
  let running = 0; // 当前并发数

  const processQueue = async () => {
    while (queue.length > 0 && running < maxConcurrency) {
      const request = queue.shift(); // 从队列中取出请求
      running++;
      await request(); // 执行请求
      running--;
      processQueue(); // 递归调用,继续处理队列中的请求
    }
  };

  processQueue(); // 初始化请求队列处理
}

// 示例:模拟 8 个请求,最多并发 2 个
const requests = Array.from(
  { length: 8 },
  (_, i) => () =>
    new Promise((resolve) => {
      console.log(`Request ${i + 1} started`);
      setTimeout(() => {
        console.log(`Request ${i + 1} finished`);
        resolve();
      }, Math.random() * 2000); // 随机延时模拟请求
    })
);

controlConcurrency(requests, 2);
解释:
  • queue 存储所有待请求的任务,每次从队列中取出一个任务并执行。
  • running 控制当前正在执行的请求数量,确保并发数不会超过 maxConcurrency
  • 当并发数低于最大限制时,自动从队列中获取新的任务并执行。

3. 使用第三方库(如 p-limit

使用第三方库,如 p-limit,可以方便地控制并发数量,限制同时运行的 Promise 数量。

示例代码:
bash 复制代码
npm install p-limit
javascript 复制代码
const pLimit = require("p-limit");
const limit = pLimit(2); // 设置最大并发数量为 2

const requests = Array.from(
  { length: 8 },
  (_, i) => () =>
    new Promise((resolve) => {
      console.log(`Request ${i + 1} started`);
      setTimeout(() => {
        console.log(`Request ${i + 1} finished`);
        resolve();
      }, Math.random() * 2000); // 随机延时模拟请求
    })
);

// 使用 p-limit 限制并发
const limitedRequests = requests.map((request) => limit(request));

Promise.all(limitedRequests).then(() => console.log("All requests completed"));
解释:
  • p-limit 库让你可以创建一个限制并发数的函数。每个请求都被包装在 limit() 中,最大并发数为 2。
  • 使用 Promise.all() 来等待所有请求完成。

总结:

  1. 使用 async/awaitPromise :逐个发起请求,确保每次只有 maxConcurrency 个并发请求。
  2. 使用队列:通过队列来管理请求,确保在并发数限制下按顺序发起请求。
  3. 使用第三方库(如 p-limit:使用库来简化并发控制,限制同时运行的 Promise 数量。

这些方法都能有效控制并发请求的数量,避免一次性发送过多请求导致性能问题。根据你的需求选择最适合的方式。

埋点mdn还没加载出来,怎么对页面之前的操作进行一个记录?

如果在页面加载过程中,埋点的 MDN 相关脚本(如 JavaScript 或分析工具)尚未加载完成,你可以通过以下几种方式来对页面之前的操作进行记录:

1. 使用本地存储(LocalStorage 或 SessionStorage)

在 MDN 脚本加载之前,可以将用户的操作记录到 localStoragesessionStorage 中,等到 MDN 脚本加载完成后再将这些记录发送到服务器或分析工具。

方案:
  1. 在用户执行的操作时,记录到 localStoragesessionStorage
  2. 等 MDN 脚本加载完成后,再从存储中获取数据并进行提交。
示例:
javascript 复制代码
// 用户操作时记录到 sessionStorage
function trackAction(action) {
  const actions = JSON.parse(sessionStorage.getItem("actions")) || [];
  actions.push(action);
  sessionStorage.setItem("actions", JSON.stringify(actions));
}

// MDN 脚本加载完成后处理操作记录
function processStoredActions() {
  const actions = JSON.parse(sessionStorage.getItem("actions"));
  if (actions) {
    actions.forEach((action) => {
      // 向埋点工具发送数据
      sendToAnalytics(action);
    });
  }
  sessionStorage.removeItem("actions");
}

// 模拟发送数据到分析工具
function sendToAnalytics(action) {
  console.log("Sending action to analytics:", action);
}

// 示例:用户点击按钮
document.getElementById("btn").addEventListener("click", function () {
  trackAction("Button Clicked");
});

// 假设 MDN 脚本加载完毕时,调用 processStoredActions
window.addEventListener("load", processStoredActions);

2. 使用队列(Queue)

在 MDN 脚本加载前,将所有操作放入一个队列中,等脚本加载完成后再统一发送请求。

方案:
  1. 将用户的操作推入一个队列中。
  2. 等 MDN 脚本加载完成后,遍历队列并发送操作记录。
示例:
javascript 复制代码
// 创建一个操作队列
const actionQueue = [];

// 用户操作时推送到队列
function trackAction(action) {
  actionQueue.push(action);
}

// MDN 脚本加载完成后处理操作队列
function processQueue() {
  actionQueue.forEach((action) => {
    // 向埋点工具发送数据
    sendToAnalytics(action);
  });
  actionQueue.length = 0; // 清空队列
}

// 模拟发送数据到分析工具
function sendToAnalytics(action) {
  console.log("Sending action to analytics:", action);
}

// 示例:用户点击按钮
document.getElementById("btn").addEventListener("click", function () {
  trackAction("Button Clicked");
});

// MDN 脚本加载完毕时,调用 processQueue
window.addEventListener("load", processQueue);

3. 使用 setTimeoutsetInterval 检测 MDN 脚本加载

你可以定期检查 MDN 埋点脚本是否已经加载完成,加载完成后立即发送存储的操作记录。

方案:
  1. 使用 setTimeoutsetInterval 定期检查 MDN 脚本是否已加载。
  2. 如果加载完成,将之前的操作记录发送到 MDN。
示例:
javascript 复制代码
let mdnLoaded = false;
const actionQueue = [];

// 检查 MDN 脚本是否加载
function checkMDNScript() {
  if (typeof window.mdnAnalytics !== "undefined") {
    // 假设 mdnAnalytics 是埋点工具的对象
    mdnLoaded = true;
    processQueue();
  }
}

// 用户操作时推送到队列
function trackAction(action) {
  actionQueue.push(action);
}

// MDN 脚本加载完毕后处理操作队列
function processQueue() {
  if (mdnLoaded) {
    actionQueue.forEach((action) => {
      sendToAnalytics(action);
    });
    actionQueue.length = 0; // 清空队列
  } else {
    setTimeout(checkMDNScript, 100); // 每100ms检测一次
  }
}

// 模拟发送数据到分析工具
function sendToAnalytics(action) {
  console.log("Sending action to analytics:", action);
}

// 示例:用户点击按钮
document.getElementById("btn").addEventListener("click", function () {
  trackAction("Button Clicked");
});

// 初始化检测 MDN 脚本
checkMDNScript();

4. 将用户操作缓存在全局变量中

可以将用户的操作存储在全局变量中,并在 MDN 脚本加载完成后处理这些数据。

方案:
  1. 定义一个全局变量来存储用户的操作。
  2. 在 MDN 脚本加载后,遍历全局变量中的操作记录,发送数据。
示例:
javascript 复制代码
// 存储用户操作
window.userActions = [];

// 用户操作时记录到全局变量
function trackAction(action) {
  window.userActions.push(action);
}

// MDN 脚本加载完成后处理操作记录
function processUserActions() {
  if (window.userActions && window.userActions.length > 0) {
    window.userActions.forEach((action) => {
      sendToAnalytics(action);
    });
    window.userActions = []; // 清空记录
  }
}

// 模拟发送数据到分析工具
function sendToAnalytics(action) {
  console.log("Sending action to analytics:", action);
}

// 示例:用户点击按钮
document.getElementById("btn").addEventListener("click", function () {
  trackAction("Button Clicked");
});

// 模拟MDN脚本加载完成时调用
window.addEventListener("load", processUserActions);

总结:

  • 队列/缓存:在 MDN 脚本加载前将用户操作存储起来,脚本加载完成后再发送数据。
  • 检查加载状态 :使用 setIntervalsetTimeout 监控 MDN 脚本加载状态,确保脚本加载完成后处理缓存的请求。

这些方法都能确保在 MDN 脚本加载完成之前,用户的操作不会丢失,并能够及时进行埋点记录。

秒杀按钮倒计时如何更精准

为了确保 秒杀按钮倒计时 更加精准,避免由于 JavaScript 执行延迟等问题引起的误差,通常的做法是基于 服务器时间 来计算倒计时。下面是一个完整的实现思路以及具体的代码示例。

实现思路:

  1. 获取服务器时间:服务器时间比本地时间更为准确,因此应该在前端获取服务器的时间戳,并根据服务器时间计算倒计时。
  2. 前端倒计时计算:根据服务器返回的时间戳,计算当前时间与目标时间(如秒杀结束时间)之间的差异,并更新前端显示。
  3. 同步更新:每秒钟更新时间,确保倒计时更新实时并且精确。

具体实现步骤:

  1. 获取服务器时间戳
    • 通过向服务器发送请求,获取当前的服务器时间戳。
  2. 计算倒计时
    • 计算当前时间与活动结束时间的差值,并显示剩余时间。
  3. 更新倒计时
    • 每秒更新一次倒计时,并根据剩余时间动态更新按钮状态(比如显示 "即将开始" 或 "已结束")。

完整的示例代码:

前端代码:
html 复制代码
<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>秒杀倒计时</title>
    <style>
      .countdown {
        font-size: 20px;
        font-weight: bold;
      }

      .btn {
        padding: 10px 20px;
        font-size: 16px;
        cursor: pointer;
      }

      .disabled {
        background-color: #ccc;
        cursor: not-allowed;
      }

      .active {
        background-color: #ff4d4f;
        color: white;
      }
    </style>
  </head>
  <body>
    <h1>秒杀倒计时</h1>
    <p>剩余时间:<span id="countdown" class="countdown"></span></p>
    <button id="seckillBtn" class="btn disabled" disabled>立即抢购</button>

    <script>
      // 目标秒杀结束时间(假设目标时间是2023年12月31日 23:59:59)
      const endTime = new Date("2023-12-31T23:59:59").getTime();

      // 获取服务器时间
      async function getServerTime() {
        try {
          const response = await fetch("/api/server-time"); // 替换为实际的服务器 API 地址
          const data = await response.json();
          return data.timestamp * 1000; // 服务器时间返回的是秒,需要转换为毫秒
        } catch (error) {
          console.error("获取服务器时间失败:", error);
          return Date.now(); // 如果失败,使用本地时间
        }
      }

      // 更新倒计时
      function updateCountdown(serverTime) {
        const currentTime = serverTime || Date.now();
        const remainingTime = endTime - currentTime;

        if (remainingTime <= 0) {
          // 秒杀结束
          document.getElementById("countdown").textContent = "秒杀结束";
          document.getElementById("seckillBtn").classList.add("disabled");
          document.getElementById("seckillBtn").disabled = true;
        } else {
          // 计算剩余时间(天、小时、分钟、秒)
          const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
          const hours = Math.floor(
            (remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
          );
          const minutes = Math.floor(
            (remainingTime % (1000 * 60 * 60)) / (1000 * 60)
          );
          const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);

          document.getElementById(
            "countdown"
          ).textContent = `${days}天 ${hours}小时 ${minutes}分钟 ${seconds}秒`;

          // 每秒更新倒计时
          setTimeout(() => updateCountdown(serverTime + 1000), 1000);
        }
      }

      // 启动倒计时
      async function startCountdown() {
        const serverTime = await getServerTime();
        updateCountdown(serverTime);
      }

      // 启动倒计时
      startCountdown();

      // 启动秒杀按钮(例如:秒杀开始时启用)
      function enableSeckillButton() {
        document.getElementById("seckillBtn").classList.remove("disabled");
        document.getElementById("seckillBtn").disabled = false;
      }

      // 假设秒杀在倒计时结束后立即启动
      setTimeout(enableSeckillButton, endTime - Date.now());
    </script>
  </body>
</html>

解释:

  1. 服务器时间获取

    • 我们通过 fetch 向服务器发送请求,获取服务器当前的时间戳。服务器时间比客户端时间更准确,因此我们根据服务器时间来计算倒计时。
    • 服务器返回的时间戳是以秒为单位的,所以在前端需要乘以 1000 转换为毫秒。
  2. 倒计时计算

    • 计算从当前时间到目标结束时间之间的剩余时间。
    • 根据剩余时间,我们计算出 天数、小时数、分钟数、秒数,并更新显示。
  3. 更新按钮状态

    • 在倒计时结束后,我们禁用秒杀按钮,并将按钮的文本更改为 "秒杀结束"。
    • 在秒杀开始时,我们启用秒杀按钮,并将其文本设置为 "立即抢购"。
  4. 倒计时更新

    • 每秒钟更新一次倒计时,保证显示实时的剩余时间。通过 setTimeout 每秒调用一次更新函数。
  5. 模拟秒杀开始

    • 在倒计时结束后,通过 setTimeout 启用秒杀按钮,模拟秒杀开始的场景。

为什么使用服务器时间:

  • 客户端时间不准:用户的设备时间可能与服务器时间不同,且受到时区、手动更改等因素的影响。通过获取服务器时间,我们确保倒计时的准确性,避免用户通过修改本地时间来操控倒计时。
  • 服务器时间一致性:所有用户的倒计时将从一个统一的标准时间开始,避免不同用户看到不同的倒计时结果。

这种方式确保了倒计时的准确性,并且在秒杀活动中能精准地控制按钮的启用和禁用。

相关推荐
codingandsleeping25 分钟前
前端工程化之webpack(万字)
前端·javascript
别惹CC1 小时前
【分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
redis·分布式·后端
Jiude1 小时前
UnoCSS presetWind4() 背景色使用 color-mix() 的原因及解决方案
前端·css
无名之逆2 小时前
Hyperlane:Rust 生态中的轻量级高性能 HTTP 服务器库,助力现代 Web 开发
服务器·开发语言·前端·后端·http·面试·rust
江沉晚呤时2 小时前
使用 .NET Core 实现 RabbitMQ 消息队列的详细教程
开发语言·后端·c#·.netcore
范哥来了2 小时前
python web开发django库安装与使用
前端·python·django
jay丿2 小时前
使用 Django 的 `FileResponse` 实现文件下载与在线预览
后端·python·django
Cloud_.2 小时前
Spring Boot 集成高德地图电子围栏
java·spring boot·后端
烛阴2 小时前
JavaScript 的 “new Function”:你不知道的黑魔法,让代码更灵活!
前端·javascript
ConardLi2 小时前
发布第五天,我的开源项目突破 1.7 K Star!
前端·javascript·人工智能