暑期实习面经总结

腾讯暑期一面凉经

tcp慢启动阈值确定

默认情况应该由接收方确定,或者根据RFC建议设置为无限大,直到首次丢包
在实际应用中,比如linux,是设置了10xMss。

页中断策略和虚拟内存技术联系

当内存中没有我们需要的数据页,会触发缺页中断,将我们需要的数据页拉入内存,其中会涉及一些页面置换算法,LRU,LFU,Clock。

关于如何确定JVM的一些启动参数

堆内存

Xms初始堆内存 Xmx最大堆内存
最好是设置为系统的内存50%-70%
预留给操作系统,线程栈,方法区一定的内存

新生代老年代的内存设置

默认是1:2
对于web应用程序,短周期的这个对象比较多,新生代的内存就可以分配的大一些,可以设置为1:1
对于一些缓存应用,长生命周期的对象比较多,就要增大老年代的比列,可以设为1:3

垃圾回收器的选择

​GC算法​​适用场景​​关键参数​
​G1 GC​通用场景(平衡吞吐量和延迟)-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4
​ZGC/Shenandoah​超大堆内存(>32G)、低延迟需求-XX:+UseZGC -Xmx16g 或 -XX:+UseShenandoahGC
​Parallel GC​高吞吐量(批处理任务)-XX:+UseParallelGC -XX:ParallelGCThreads=8 -XX:MaxGCPauseMillis=500
​CMS​老年代低延迟(已逐步被G1替代)-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75

10亿个数字找top1000(topk问题)

//其实是最小堆的思想,java中有优先级队列可以使用
public class Top1000 {
    //优先级队列
    public static List<Integer> top1000(List<Integer> list) {

        PriorityQueue<Integer> queue = new PriorityQueue<>((a,b)->list.get(b)-list.get(a));
        for (int i = 0; i < list.size(); i++) {
            if (queue.size()<1000){
                queue.add(i);
            }else if (list.get(i)>list.get(queue.peek())){
                queue.remove();
                queue.add(i);
            }
        }
        List<Integer> result= new ArrayList<>();
        while (!queue.isEmpty()){
            result.add(queue.poll());
        }
        return result;
    }


}


//也可以自己构建最小堆
public class MinHeap {
    private int[] heap;
    private int size;
    private final int capacity;

    public MinHeap(int capacity) {
        this.capacity = capacity;
        this.heap = new int[capacity];
        this.size = 0;
    }

    public void add(int num) {
        if (size < capacity) {
            // 堆未满时插入末尾并上浮
            heap[size] = num;
            siftUp(size);
            size++;
        } else if (num > heap[0]) {
            // 替换堆顶并下沉
            heap[0] = num;
            siftDown(0);
        }
    }

    private void siftUp(int index) {
        while (index > 0) {
            int parent = (index - 1) / 2;
            if (heap[index] >= heap[parent]) break;
            swap(index, parent);
            index = parent;
        }
    }

    private void siftDown(int index) {
        int smallest = index;
        int left = 2 * index + 1;
        int right = 2 * index + 2;

        if (left < size && heap[left] < heap[smallest]) {
            smallest = left;
        }
        if (right < size && heap[right] < heap[smallest]) {
            smallest = right;
        }

        if (smallest != index) {
            swap(index, smallest);
            siftDown(smallest);
        }
    }

    private void swap(int i, int j) {
        int temp = heap[i];
        heap[i] = heap[j];
        heap[j] = temp;
    }

    public List<Integer> getSortedResult() {
        // 将堆排序并逆序输出
        int[] copy = Arrays.copyOf(heap, size);
        Arrays.sort(copy);
        List<Integer> result = new ArrayList<>();
        for (int i = copy.length - 1; i >= 0; i--) {
            result.add(copy[i]);
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums = new int[1_000_000_000]; // 假设已初始化数据

        MinHeap minHeap = new MinHeap(1000);
        for (int num : nums) {
            minHeap.add(num);
        }

        List<Integer> top1000 = minHeap.getSortedResult();
        System.out.println("Top 1000 numbers: " + top1000);
    }
    
}

快排和归并排序

​排序算法​​最佳时间复杂度​​平均时间复杂度​​最差时间复杂度​​空间复杂度​​是否稳定​​是否原地排序​
​冒泡排序​O(n)O(n²)O(n²)O(1)
​选择排序​O(n²)O(n²)O(n²)O(1)
​插入排序​O(n)O(n²)O(n²)O(1)
​希尔排序​O(n log n)O(n log² n)O(n²)O(1)
​快速排序​O(n log n)O(n log n)O(n²)O(log n)~O(n)
​归并排序​O(n log n)O(n log n)O(n log n)O(n)
​堆排序​O(n log n)O(n log n)O(n log n)O(1)
​计数排序​O(n + k)O(n + k)O(n + k)O(n + k)
​桶排序​O(n + k)O(n + k)O(n²)O(n + k)
​基数排序​O(n × k)O(n × k)O(n × k)O(n + k)
//快速排序
确定一个基准,主要是使用双指针,一次确定一个整数的位置,还是分治思想
public class QuickSort {
    public void quickSort(int []nums,int start,int end){
        if (start<end){
            int base=nums[start];
            int left=start;
            int right=end;

            while (left<right){
                while (left<right&&nums[right]>=base){
                    right--;
                }
                nums[left]=nums[right];
                while (left<right&&nums[left]<=base){
                    left++;
                }
                nums[right]=nums[left];
            }
            nums[left]=base;
            quickSort(nums,start,left-1);
            quickSort(nums,left+1,end);
        }
    }
}

//归并排序
先分,再并,需要借助额外的存储空间,真正的排序步骤在合并这里
public class MergeSort {


    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length <= 1) return;
        int[] temp = new int[arr.length]; // 预分配临时数组
        sort(arr, 0, arr.length - 1, temp);
    }


   private static void sort(int[] arr, int left, int right, int[] temp) {
        if (left>=right)
            return;
        int mid=left+(right-left)/2;
        sort(arr,left,mid,temp);
        sort(arr,mid+1,right,temp);
        merge( left,mid,right,temp,arr);
    }

    private static void merge(int left, int mid, int right, int[] temp,int[] arr) {
        int l=left;
        int r=mid+1;
        int t=0;
        while (l<=mid&&r<=right){
            if (arr[l]<=arr[r])
                temp[t++]=arr[l++];
            else
                temp[t++]=arr[r++];
        }

        while (l<=mid){
            temp[t++]=arr[l++];
        }
        while (r<=right){
            temp[t++]=arr[r++];
        }

        t=0;
        while (t<=right)
            arr[left++]=temp[t++];
    }

    // 测试用例
    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7};
        System.out.println("排序前: " + Arrays.toString(arr));

        mergeSort(arr);

        System.out.println("排序后: " + Arrays.toString(arr));
        // 输出: 排序后: [5, 6, 7, 11, 12, 13]
    }
}

时间复杂度和空间复杂度的计算方法

空间复杂度

​复杂度等级​​典型场景​​示例算法​
​O(1)​仅用固定数量变量交换两数、数组遍历
​O(n)​存储与输入规模正相关的数据结构数组拷贝、哈希表存n个元素
​O(n²)​二维结构(如矩阵、DP表)动态规划中的二维数组
​O(log n)​递归分治(每次问题规模减半)二分查找的递归实现

对于递归操作,还是要看他的递归深度

时间复杂度
对于常见的操作可以列表计算

对于递归
形如T(N) = a* T(N/b)+ O(N^d)(其中a、b、d都是常数)的递归函数可以直接通过Master公式来确定时间复杂度其中a是递归路数,b是递归问题规模(理解成每次把整体问题规模分解成的子问题规模),O(N^d)是除递归操作外的时间复杂度
如果
1.log(b,a)<d,复杂度为O(N^d)

2.log(b,a)>d,复杂度为O(N^log(b,a))

3.log(b,a)=d,复杂度为O(N^d*logn)

快重传和快恢复

快重传(Fast Retransmit)与快恢复(Fast Recovery)分析

快重传和快恢复是 TCP Reno 算法中解决网络拥塞的核心机制,旨在减少丢包恢复时间并提高网络吞吐量。以下是两者的详细解析及其协同工作原理:


1. 快重传(Fast Retransmit)

触发条件
  • 发送方连续收到 3个重复ACK(Dup-ACK),表明某个数据包已丢失,但后续数据包仍能到达接收方。
核心逻辑
  • 立即重传:检测到3个Dup-ACK后,无需等待超时(RTO),直接重传丢失的数据包。
  • 避免超时等待:传统TCP Tahoe在丢包时需等待超时触发重传,而快重传显著降低恢复延迟。
示例场景
发送方发送数据包:1, 2, 3, 4, 5  
接收方收到:1, 3, 4, 5 → 连续返回ACK=2(3次)  
发送方立即重传数据包2。

2. 快恢复(Fast Recovery)

目标
  • 在重传丢失包后,避免拥塞窗口(cwnd)重置为1(传统慢启动策略),而是平滑恢复发送速率。
关键步骤
  1. 调整窗口
    ssthresh = max(cwnd / 2, 2 * MSS)
    cwnd = ssthresh + 3 * MSS (补偿已确认的3个Dup-ACK对应的数据包)。
  2. 线性增长
    • 后续每收到一个Dup-ACK,cwnd += 1 MSS(允许发送新数据包,避免管道枯竭)。
  3. 退出条件
    • 收到新数据的ACK时,将cwnd设为ssthresh,进入拥塞避免阶段(线性增长)。
优势
  • 避免吞吐量骤降:传统TCP Tahoe在丢包后重置cwnd=1,导致吞吐量断崖式下跌;快恢复通过保持较高cwnd,维持网络利用率。

3. 快重传与快恢复的协同流程

[发送数据] → [收到3个Dup-ACK] → 触发快重传 → 进入快恢复阶段 → 调整cwnd/ssthresh → 线性增长直至新ACK到达 → 退出快恢复

4. 与传统策略(TCP Tahoe)对比

机制TCP TahoeTCP Reno(快重传+快恢复)
丢包响应仅依赖超时重传快重传触发快速重传
窗口调整超时后cwnd=1,重启慢启动快恢复保持cwnd,进入拥塞避免
吞吐量影响高延迟,低恢复效率低延迟,高吞吐量保持

5. 实际应用与优化

  • 重复ACK阈值:Linux内核允许通过参数调整触发快重传的Dup-ACK次数(默认3次,sysctl net.ipv4.tcp_reordering)。
  • 部分确认处理:若在快恢复期间收到部分新ACK(非完全恢复),可能触发多次快重传。
  • 与现代算法结合:CUBIC等算法保留快重传机制,但使用不同拥塞窗口增长策略(如三次函数)。

6. 典型问题与调优

  • 过早退出快恢复:若网络仍拥塞,可能反复触发快重传,需结合RTT动态调整阈值。
  • 乱序与丢包区分:网络乱序也可能导致Dup-ACK,可通过时间戳选项(TCP Timestamps)优化判断逻辑。

总结

快重传通过3个Dup-ACK触发快速重传,快恢复通过动态调整cwnd避免吞吐量暴跌,二者协同实现了高效丢包恢复与网络资源利用。此机制是TCP Reno的核心改进,奠定了现代拥塞控制算法的基础。在实际网络环境中,需结合具体协议栈参数(如Linux内核配置)和网络特性(如RTT、乱序率)进一步调优。

Mysql InnoDB引擎的加锁原则

加锁的基本单位是邻键锁next-keylock
查找过程中访问到的对象才会加锁
唯一索引等值查询,邻键锁会退化成记录锁
唯一索引和非唯一索引在等值查询上的区别是,唯一索引在遍历到第一不符合查询条件的值时就停止,不会再往后遍历,而普通索引就还会往后遍历记录第一不满足查询条件的这个记录,将这个作为右边界。


字节国际电商与数字化一面

分布式锁实现原理

在分布式系统中,网络分区(Network Partition)可能导致多个客户端同时持有同一资源的锁,从而破坏互斥性。这一问题通常被称为“脑裂”(Split-Brain)。以下是详细解释及解决方案:


一、问题背景

场景示例

  1. 系统架构:假设一个分布式锁服务由3个节点(Node A、B、C)组成,客户端需要多数节点(≥2)确认才能获取锁。
  2. 网络分区发生:网络故障导致节点被分割为两组:
    子网络1:Node A + 客户端1。
    子网络2:Node B、C + 客户端2。
  3. 锁竞争
    • 客户端1在子网络1中成功获取锁(Node A确认)。
    • 客户端2在子网络2中成功获取同一资源的锁(Node B、C确认)。
  4. 结果:两个客户端同时持有锁,导致数据不一致。

二、根本原因

  1. 分区期间的状态隔离:子网络之间无法同步锁状态,各自独立决策。
  2. 锁超时机制失效:若客户端无法续期锁,但分区恢复前其他客户端误判锁已释放。
  3. 多数派协议的局限性:RedLock等算法依赖多数节点响应,但分区可能导致多数派分布在多个子网络中。

三、解决方案

1. 多数派协议(如RedLock)

  • 原理:客户端需在多数节点(N/2+1)上成功获取锁,确保分区后仅一个子网络满足多数条件。
  • 示例:5节点集群,分区为3+2:
    • 只有包含3节点的子网络能形成多数派,允许获取锁。
    • 另一子网络无法满足多数条件,拒绝加锁请求。
  • 缺点
    • 分区可能导致系统不可用(如5节点分3+2时,3节点子网络可工作,但2节点子网络无法处理新请求)。
    • 时钟漂移问题可能导致锁提前释放(需配合NTP同步)。

2. 租约机制(如etcd/ZooKeeper)

  • 原理:锁绑定租约(Lease),客户端需定期续期(KeepAlive)。若续期失败(如网络分区),租约到期后锁自动释放。
  • 流程
  1. 客户端1获取锁并绑定租约(30秒),每10秒续期。
  2. 网络分区导致续期失败,租约到期后锁自动删除。
  3. 客户端2在分区恢复后获取锁。
  • 优点:避免分区期间锁长期被占用。
  • 缺点:业务处理时间必须小于租约时间,否则锁可能被提前释放。

3. 共识算法(如Raft/Paxos)

  • 原理:写操作需多数节点持久化确认,确保分区后仅多数派子网络可修改状态。
  • 示例:etcd基于Raft协议:
    • 写请求需多数节点确认。
    • 分区后,仅多数派子网络可选举新Leader并处理写请求。
    • 少数派子网络无法提交新操作,保持只读状态。
  • 优点:强一致性保证。
  • 缺点:复杂度高,性能较低。

4. 客户端容错设计

  • 幂等性:业务逻辑需支持重复执行,避免锁失效后重复操作导致错误。
  • 锁持有者验证:操作共享资源前,再次检查锁状态。
  if (lock.isHeldByCurrentThread()) {
      // 执行操作
  }

5. 超时时间动态调整

  • 自适应超时:根据历史业务处理时间动态调整锁超时,降低锁提前释放概率。
  • 监控告警:检测网络分区事件,触发人工介入或自动修复。

四、方案对比

方案一致性可用性复杂度适用场景
多数派协议允许偶发锁冲突的高并发场景
租约机制需要自动释放的强一致性场景
共识算法金融、交易等强一致性场景
客户端容错所有场景(必备)

五、总结

网络分区导致多客户端持锁的问题需通过协议设计、租约管理和客户端容错等多层机制解决:

  1. 协议层面:采用多数派或共识算法,限制分区期间的锁分配。
  2. 租约层面:通过心跳续期和自动释放,避免锁长期滞留。
  3. 客户端层面:设计幂等操作和锁状态二次验证。

实际选型需权衡一致性、可用性与性能,并结合业务场景定制方案(如电商秒杀用RedLock+幂等,金融交易用etcd+Raft)。

http和https的区别

在面试中回答HTTP与HTTPS的区别时,可以按照以下逻辑进行简明扼要的阐述,突出技术要点和实际场景的理解:


1. 核心区别
“HTTP是明文传输的超文本协议,而HTTPS是HTTP的安全版本,通过SSL/TLS协议对传输数据进行加密和身份认证,解决HTTP的三大安全隐患:窃听、篡改和伪装。”


2. 分点对比(技术细节)

  • 加密与安全性
    HTTP的数据未加密,传输内容可能被中间人窃听或篡改;HTTPS通过SSL/TLS加密(如AES、RSA等算法),确保数据保密性和完整性(通过哈希校验),防止中间人攻击。
  • 端口与协议层次
    HTTP默认使用80端口,HTTPS默认443端口。SSL/TLS介于传输层(如TCP)和应用层(HTTP)之间,为HTTP提供加密通道。
  • 证书与身份认证
    HTTPS需由权威CA(如Let’s Encrypt)签发数字证书,验证服务器身份,防止钓鱼网站。浏览器会展示证书信息(如小锁图标),增强用户信任。
  • 性能影响
    HTTPS因加密/解密和握手过程(如TLS四次握手)会略增延迟,但通过硬件加速(如SSL专用芯片)、会话复用和HTTP/2优化,性能差距已不明显。现代网站普遍采用HTTPS,牺牲少量性能换取安全是必要选择。
  • SEO与合规性
    搜索引擎(如Google)优先索引HTTPS页面,且现代浏览器对HTTP页面标记“不安全”。GDPR等数据法规也要求敏感信息必须加密传输。

3. 实际意义(升华回答)
“HTTPS不仅是技术升级,更是构建网络信任的基础。它防止用户密码、支付信息等泄露,提升品牌可信度。如今,Let’s Encrypt等免费证书的普及,以及HTTP/3对HTTPS的深度集成,使得全站HTTPS已成为行业标准。”


回答示例
“HTTP和HTTPS的核心区别在于安全性。HTTP传输数据是明文的,易被窃听或篡改;而HTTPS通过SSL/TLS协议加密数据,并使用数字证书验证服务器身份。例如,当用户访问网银时,HTTPS会加密交易信息,同时证书确保连接的是真实的银行服务器,而非钓鱼网站。此外,HTTPS使用443端口,且对SEO有正向影响。尽管早期因加密计算影响性能,但现代优化手段(如会话复用、硬件加速)已使性能损耗可忽略。因此,HTTPS已成为保障隐私和信任的必备技术。”


TLS和SSL

在面试中回答TLS与SSL的区别时,应结合协议演进、安全机制、性能优化和实际应用进行结构化阐述。以下是简明总结:


1. 历史与版本演进

  • SSL(Secure Sockets Layer)
    由Netscape于1990年代开发(SSL 2.0/3.0),因漏洞(如POODLE攻击)被逐步淘汰。
  • TLS(Transport Layer Security)
    TLS是SSL的标准化升级版,由IETF维护,版本包括TLS 1.0(1999)、1.2(2008)、1.3(2018)。TLS 1.0对应SSL 3.1,但不向后兼容

2. 核心区别

维度SSL(3.0及更早)TLS(1.2/1.3)
安全性❌ 弱算法(RC4、MD5)、无前向保密✅ 强算法(AES-GCM、ChaCha20)、强制前向保密(ECDHE)
性能握手慢(2-RTT)TLS 1.3优化至1-RTT甚至0-RTT
漏洞修复易受攻击(如POODLE、BEAST)修复已知漏洞,禁用不安全功能(如压缩、重协商)
前向保密不支持✅ 依赖临时密钥(如ECDHE),会话密钥独立
行业支持已被禁用(RFC 7568)成为现代标准(如HTTP/2、QUIC强制要求)

3. 实际应用中的关键点

  • 弃用SSL
    所有现代浏览器(Chrome、Firefox)已禁用SSL,仅支持TLS 1.2+。PCI DSS等合规标准要求禁用TLS 1.0/1.1。
  • 术语混淆
    “SSL证书”是历史名称,实际用于TLS协议。证书本身(X.509)兼容两者,但需服务器配置支持TLS。
  • 检测与配置
    • 工具:openssl s_clientSSL Labs测试验证协议版本。
    • 服务器配置示例(Nginx):
    nginx ssl_protocols TLSv1.2 TLSv1.3; # 禁用SSLv3、TLS 1.0/1.1 ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

4. TLS 1.3的革命性改进

  • 简化握手:合并消息步骤,1-RTT完成握手,0-RTT恢复会话(需防重放攻击)。
  • 算法精简:仅保留AEAD加密(如AES-GCM)和椭圆曲线(如X25519),弃用RSA密钥交换。
  • 强制前向保密:所有密钥交换必须使用临时密钥(如ECDHE),彻底杜绝历史密钥泄露风险。

5. 回答示例

“TLS是SSL的继任协议,核心区别有三点:

  1. 安全性:TLS禁用RC4、MD5等弱算法,强制前向保密(如ECDHE),即使服务器私钥泄露,历史通信仍安全。
  2. 性能:TLS 1.3将握手从2-RTT优化到1-RTT,并支持0-RTT快速恢复连接。
  3. 行业合规:现代标准(如PCI DSS、HTTP/2)要求使用TLS 1.2+,所有主流浏览器已禁用SSL。
    尽管‘SSL证书’一词仍在用,但它实际服务于TLS协议,且配置需明确禁用SSLv3等不安全版本。”

一、TLS与SSL的扩展知识点(应对追问)

1. 前向保密的意义

场景举例
假设某银行的服务器私钥在2024年泄露,若该银行使用TLS 1.3+ECDHE:
◦ 每次会话的临时密钥已销毁,攻击者无法解密历史交易数据(如2023年的转账记录)。
◦ 若使用传统RSA密钥交换,攻击者可用私钥解密所有历史预主密钥,导致数据全面泄露。
结论:前向保密是金融、医疗等敏感场景的合规性要求(如GDPR、PCI DSS)。

2. 协议降级攻击防御(TLS_FALLBACK_SCSV)

攻击原理:中间人强制客户端使用低版本协议(如SSL 3.0),利用旧协议漏洞解密通信。
防御机制
◦ 客户端在握手时发送TLS_FALLBACK_SCSV信号,表明这是因降级触发的重连。
◦ 服务器检测到此信号后,若客户端本可支持更高版本协议(如TLS 1.3),则拒绝连接。
示例:Chrome浏览器在检测到降级攻击时显示ERR_SSL_VERSION_OR_CIPHER_MISMATCH。

3. HSTS(HTTP Strict Transport Security)

作用:通过HTTP响应头Strict-Transport-Security: max-age=31536000; includeSubDomains,强制浏览器仅通过HTTPS(TLS)访问网站,防止SSL剥离攻击。
预加载(HSTS Preload List):将域名提交到浏览器厂商的HSTS预加载列表(如Google列表),首次访问即强制HTTPS。
实际应用:主流网站(如GitHub、PayPal)均启用HSTS。


二、RSA与ECDHE的区别(简明对比)

维度RSAECDHE
核心角色非对称加密/签名算法基于椭圆曲线的临时密钥交换算法
前向保密❌ 不支持(若用于密钥交换)✅ 强制支持(临时密钥销毁后无法回溯)
性能开销高(长密钥加解密慢,如2048位)低(短密钥,如256位ECC≈3072位RSA强度)
密钥交换流程客户端生成预主密钥,用RSA公钥加密传输双方交换临时公钥,计算共享密钥(无需传输)
安全性依赖大数分解难题椭圆曲线离散对数问题(ECDLP)
现代应用主要用于证书签名(如RSA-SHA256)TLS 1.3强制用于密钥交换

三、RSA与ECDHE的区别(详细说明)

1. 功能角色

  • RSA
    密钥交换(传统模式):客户端生成预主密钥 → 用服务器RSA公钥加密 → 传输给服务器(密钥交换与身份认证绑定)。
    数字签名:验证服务器身份(如证书中的RSA签名)。
  • ECDHE
    密钥协商:双方基于临时椭圆曲线参数生成共享密钥(预主密钥),与身份认证解耦(需额外签名算法如RSA或ECDSA)。
    前向保密:每次会话使用独立临时密钥,会话结束后销毁。

2. 安全性对比

  • RSA密钥交换的隐患
    • 若服务器RSA私钥泄露,攻击者可解密所有历史通信的预主密钥(无前向保密)。
    • 例如,2014年心脏出血漏洞(Heartbleed)导致私钥泄露,使用RSA密钥交换的网站历史数据全盘暴露。
  • ECDHE的优势
    • 临时密钥仅存续于单次会话,私钥泄露不影响历史数据。
    • 256位ECC密钥安全性≈3072位RSA,抗量子计算能力更强(目前标准下)。

3. 性能差异

  • RSA计算开销
    • RSA-2048解密比加密慢约10倍,密钥越长性能越差(如4096位RSA密钥握手延迟显著)。
    • 不适合移动设备或高并发服务器(如每秒千次握手)。
  • ECDHE优化
    • 椭圆曲线运算速度快,256位ECC密钥签名速度比RSA-2048快约5倍。
    • 例如,WhatsApp使用ECDHE-X25519实现每秒百万次密钥协商。

4. 实际配置建议

  • 禁用RSA密钥交换
  # Nginx配置示例(仅允许ECDHE)
  ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
  ssl_prefer_server_ciphers on;
  • 优先选择ECDSA证书
    ECDSA签名算法(如secp384r1)比RSA更高效,且与ECDHE天然适配。

四、总结回答示例

“RSA和ECDHE的核心区别在于密钥交换机制和安全性:

  • RSA通过公钥加密预主密钥,但缺乏前向保密,私钥泄露会导致历史通信暴露。
  • ECDHE基于临时椭圆曲线密钥生成共享密钥,每次会话独立,支持前向保密。
    性能上,ECDHE密钥更短、计算更快,适合高并发场景。现代TLS 1.3已废弃RSA密钥交换,强制使用ECDHE等前向保密算法。”

结合扩展知识点,可进一步体现对协议细节和行业实践的理解,增强面试表现。

Redis的内存处理机制

Redis 的内存处理机制是其高性能的核心之一,主要通过以下策略实现高效内存管理与优化:


1. 灵活的数据结构编码优化

Redis 为每种数据类型设计多种内部编码,根据数据规模和内容自动选择最省内存的存储方式:

  • 字符串(String)
    • 普通字符串(RAW编码)或整数(INT编码,节省内存)。
  • 哈希(Hash)
    ziplist(压缩列表):元素较少时(默认 hash-max-ziplist-entries 512)使用连续内存存储,无指针开销。
    hashtable(哈希表):元素较多时转换为哈希表,提高查询效率。
  # 示例:存储用户信息时,若字段数 ≤ 512且值大小 ≤ 64字节,自动使用ziplist
  HSET user:1000 name "Alice" age 30 email "alice@example.com"
  • 列表(List)
    ziplist:元素少且小值时使用。
    linkedlist(双向链表)quicklist(3.2+版本):组合ziplist和链表,平衡内存与性能。
  • 集合(Set)
    intset(整数集合):元素全为整数且数量少时(默认 set-max-intset-entries 512)。
    hashtable:非整数或元素过多时。
  • 有序集合(Sorted Set)
    ziplist:元素少时按分值顺序紧凑存储。
    skiplist(跳跃表)+ hashtable:元素多时组合结构,支持快速范围查询。

2. 内存分配与碎片管理

  • 内存分配器
    Redis 默认使用 jemalloc(而非 libc),因其高效的内存块分配策略,显著减少内存碎片。
  # 启动时指定内存分配器(通常无需修改)
  ./redis-server --malloc-lib jemalloc
  • 碎片整理
    自动碎片回收:通过 CONFIG SET activedefrag yes 启用后台碎片整理。
    手动触发:执行 MEMORY PURGE(依赖jemalloc)释放未用内存。

3. 过期数据清理机制

  • 惰性删除(Lazy Expire)
    访问键时检查是否过期,若过期则立即删除。避免主动扫描开销,但可能导致内存未被及时释放。
  • 定期删除(Active Expire)
    每隔 100ms 随机抽取一定数量(由 hz 配置控制)的键检查过期,逐步清理。

4. 内存淘汰策略(Eviction Policies)

当内存达到 maxmemory 限制时,根据策略淘汰数据:

  • volatile-lru:从设置了过期时间的键中淘汰最近最少使用的(LRU)。
  • allkeys-lru:从所有键中淘汰LRU,适合缓存场景。
  • volatile-lfu(4.0+):淘汰最不经常使用的(LFU),基于频率统计。
  • volatile-ttl:优先淘汰剩余存活时间(TTL)最短的键。
  • noeviction(默认):禁止淘汰,写入操作返回错误。
# 配置淘汰策略(示例设置为allkeys-lru)
CONFIG SET maxmemory-policy allkeys-lru

5. 内存优化技巧

  • 缩短键名:键名尽量简短(如 u:1000 代替 user:1000:profile)。
  • 使用Hash代替多个String:存储对象时,Hash比多个String节省内存(减少键元数据开销)。
  • 压缩存储:对长文本/二进制数据使用客户端压缩(如gzip),存入Redis后解压使用。
  • 分片(Sharding):将大数据集分散到多个Redis实例,避免单实例内存压力。

6. 监控与诊断工具

  • 内存统计
  INFO memory  # 查看内存使用详情,如碎片率(mem_fragmentation_ratio)
  MEMORY USAGE key  # 查看指定键的内存占用
  • 大键分析
    使用 redis-cli --bigkeys 扫描内存中大键,优化或拆分。
  • 内存溢出处理
    used_memory 接近 maxmemory,调整淘汰策略或扩容。

总结

Redis 通过 动态编码优化高效内存分配智能淘汰策略碎片管理 等机制,在保证性能的同时最大化内存利用率。实际应用中需结合监控数据调整 maxmemory、淘汰策略和数据结构配置,以应对不同场景的内存挑战。

排查慢sql

在面试中回答如何排查慢SQL时,可以按照以下结构化步骤进行阐述,以展现系统化的排查思路和专业知识:


1. 确认问题并收集数据

  • 开启慢查询日志
    检查数据库的慢查询日志是否开启(如MySQL的slow_query_log),并设置合理的阈值(long_query_time,通常从1秒开始)。通过日志定位具体是哪些SQL语句执行缓慢。
  • 使用分析工具
    利用工具解析慢查询日志,例如:
    mysqldumpslow(MySQL内置)。
    pt-query-digest(Percona Toolkit),生成报告并排序执行时间、频率高的SQL。

2. 分析SQL执行计划

  • 使用EXPLAIN命令
    对目标SQL执行EXPLAINEXPLAIN FORMAT=JSON,关注以下字段:
    type:扫描类型(ALL全表扫描需优化)。
    key:是否使用索引。
    rows:预估扫描行数。
    Extra:额外信息(如Using temporaryUsing filesort可能需优化)。
  • 索引有效性检查
    • 确认查询条件字段是否有合适索引(SHOW INDEX FROM table)。
    • 检查索引选择性(区分度低的字段如性别,索引效果差)。

3. 检查数据库状态与资源

  • 监控实时负载
    SHOW FULL PROCESSLIST:查看当前运行的查询,确认是否有阻塞(如长时间运行的查询或锁等待)。
    SHOW ENGINE INNODB STATUS:分析InnoDB状态,检查锁竞争或死锁。
  • 服务器资源瓶颈
    CPU/内存:通过tophtop确认是否资源饱和。
    磁盘IO:监控工具(如iostat)确认是否存在高IO等待。

4. 优化SQL与索引

  • 重写SQL语句
    • 避免SELECT *,减少数据传输。
    • 拆分复杂查询,优化子查询或JOIN(如改用INNER JOIN并确保关联字段有索引)。
    • 分页优化:避免大OFFSET,改用WHERE id > {last_id}
  • 索引优化
    • 添加缺失索引,注意联合索引的最左匹配原则。
    • 删除冗余索引,减少写操作开销。
    • 处理索引失效场景(如隐式类型转换、函数操作字段)。

5. 检查配置与统计信息

  • 数据库配置调优
    • 确认innodb_buffer_pool_size是否足够大(通常设为物理内存的70-80%)。
    • 调整连接数(max_connections避免过高导致资源争抢)。
  • 更新统计信息
    使用ANALYZE TABLE更新表的统计信息,确保优化器生成高效执行计划。

6. 架构与业务层优化

  • 缓存与读写分离
    • 引入缓存(如Redis),减少重复查询。
    • 读写分离,分散主库压力。
  • 分库分表
    针对大数据量表,考虑水平拆分或按时间归档历史数据。

7. 复验与持续监控

  • 验证优化效果
    优化后重新执行SQL,对比执行时间,确认是否达到预期。
  • 建立监控告警
    使用Prometheus+Grafana等工具监控数据库性能,设置慢查询阈值告警。

总结回答示例

“排查慢SQL时,我会首先通过慢查询日志定位问题SQL,结合EXPLAIN分析执行计划,检查索引使用情况。同时观察数据库实时状态和资源使用,排除锁竞争或硬件瓶颈。根据分析结果优化SQL语句或索引,必要时调整配置或引入缓存、分库分表等架构方案。最后通过测试和监控确保优化效果。”


通过以上步骤,能够系统化地覆盖慢SQL排查的关键点,展现全面的问题解决能力。

排查OutOfMemory问题

排查OutOfMemory(OOM)问题需要系统性地分析内存使用情况并定位根本原因。以下是详细的步骤指南:


1. 确认OOM类型与错误信息

  • 查看日志和错误堆栈
    • 确定是哪种OOM错误(例如Java中的OutOfMemoryError: Java heap spaceOutOfMemoryError: Metaspace)。
    • 检查是否有明确的代码位置触发错误。
  • 区分内存区域
    堆内存(Heap):对象实例存储区域。
    非堆内存(Non-Heap):如元空间(Metaspace)、线程栈、直接内存(Direct Buffer)等。

2. 监控与分析内存使用

  • 实时监控工具
    JVM:使用jstatjconsoleVisualVMPrometheus + Grafana监控堆/非堆内存变化。
    系统级tophtopvmstat观察物理内存和交换分区使用。
  • 生成和分析堆转储(Heap Dump)
    • 通过JVM参数(-XX:+HeapDumpOnOutOfMemoryError)自动生成堆转储。
    • 使用工具(如Eclipse MAT、JProfiler)分析堆转储,定位大对象或内存泄漏点。

3. 检查内存配置

  • JVM参数调优
    堆内存:调整-Xmx(最大堆)和-Xms(初始堆),例如: java -Xmx4g -Xms4g -jar app.jar元空间:调整-XX:MetaspaceSize-XX:MaxMetaspaceSize(默认无限制)。
    直接内存:检查-XX:MaxDirectMemorySize(NIO使用)。
  • 容器环境(如Docker/K8s)
    • 确认容器的内存限制(--memory)是否合理,避免容器因内存不足被OOM Killer终止。

4. 定位内存泄漏

  • 分析对象引用链
    • 在堆转储中查找占用内存最大的对象(如char[]HashMap$Node等)。
    • 查看这些对象的GC Root引用路径,确认是否有预期外的强引用(如静态集合类缓存未清理)。
  • 代码审查
    • 检查是否有未关闭的资源(如数据库连接、文件流)。
    • 避免静态集合类无限增长(例如全局缓存未设置过期策略)。
  • 第三方库问题
    • 升级存在已知内存泄漏问题的库版本。
    • 使用工具(如Java Agent)跟踪第三方库的内存分配。

5. 优化内存使用

  • 减少对象创建
    • 复用对象(如对象池、线程局部变量)。
    • 避免在循环中频繁创建临时对象(如字符串拼接改用StringBuilder)。
  • 调整数据加载策略
    • 分页读取大数据集,避免一次性加载到内存。
    • 使用流式处理(Streaming)替代全量缓存(如数据库结果集逐行处理)。
  • 优化数据结构
    • 使用更节省内存的数据结构(如Trove库替代JDK集合)。
    • 压缩存储数据(如使用byte[]替代字符串)。

6. 垃圾回收(GC)调优

  • 选择合适的GC算法
    • 低延迟场景:G1 GC(-XX:+UseG1GC)或ZGC(-XX:+UseZGC)。
    • 高吞吐场景:Parallel GC(-XX:+UseParallelGC)。
  • 调整GC参数
    • 增大新生代比例(-XX:NewRatio)。
    • 调整GC触发阈值(如-XX:InitiatingHeapOccupancyPercent)。
    • 启用GC日志分析(-Xlog:gc*)。

7. 非堆内存排查

  • 元空间(Metaspace)溢出
    • 检查是否动态生成大量类(如反射、CGLIB代理)。
    • 调整-XX:MaxMetaspaceSize并监控类加载情况。
  • 直接内存溢出
    • 排查NIO操作(如ByteBuffer.allocateDirect())是否未释放。
    • 使用-XX:MaxDirectMemorySize限制直接内存。
  • 线程栈溢出
    • 检查线程数是否过多(jstack查看线程状态)。
    • 调整线程栈大小(-Xss),但需谨慎避免内存浪费。

8. 系统与架构优化

  • 分布式架构
    • 拆分单体应用为微服务,分散内存压力。
    • 使用外部缓存(如Redis)替代本地缓存。
  • 限流与降级
    • 通过熔断机制(如Hystrix)防止突发流量压垮服务。
    • 限制大查询或批量操作的内存占用。

9. 复现与测试

  • 压力测试
    • 使用JMeter、Gatling模拟高并发场景,观察内存使用情况。
    • 对比优化前后的内存消耗。
  • 持续监控
    • 在生产环境部署APM工具(如SkyWalking、New Relic)实时跟踪内存指标。

总结回答示例

“排查OOM问题需要结合日志分析、堆转储检查和内存监控。首先确认是堆内存还是非堆内存溢出,通过工具定位大对象或泄漏点。检查JVM配置是否合理,优化代码减少内存占用,并调整GC策略。对于容器环境需验证资源限制,架构上可引入缓存和分布式方案降低单节点压力。”


通过以上步骤,可以系统化地定位并解决OOM问题,确保应用稳定运行。

手撕:两两交换链表节点

//这个当时边界判断有问题,引入虚拟头节点的作用是在三个节点的范围内两两交换后两个节点,这样也可以避免循环引用问题。

public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next=head;
       ListNode temp=dummyHead;
        while(temp.next!=null&&temp.next.next!=null){
            ListNode node1=temp.next;
            ListNode node2=temp.next.next;
            ListNode temp1=node2.next;
            temp.next=node2;
            temp.next.next=node1;
            temp.next.next.next=temp1;
            temp=node1;
        }
        return dummyHead.next;
    }

using tempetory 临时表问题

在排查慢SQL时,若通过EXPLAIN发现Extra字段显示Using temporary,表明该查询需要创建临时表来完成操作(如排序、分组或连接)。以下是优化此类问题的系统性思路及具体方法:


1. 确认问题原因
  • 临时表的常见触发场景
    GROUP BYORDER BY的列不一致或未使用索引。
    DISTINCTUNION或子查询导致中间结果存储。
    • 复杂的JOIN操作(如未使用索引的关联)。

2. 优化策略与实施步骤
(1) 优化查询结构
  • 统一GROUP BYORDER BY的列
    若两者列不一致,MySQL可能需先分组再排序,导致临时表。
    优化示例
  -- 原查询(GROUP BY和ORDER BY列不一致)
  SELECT category_id, SUM(amount) 
  FROM orders 
  GROUP BY category_id 
  ORDER BY created_at;

  -- 优化后:统一排序和分组列(或移除不必要的排序)
  SELECT category_id, SUM(amount) 
  FROM orders 
  GROUP BY category_id 
  ORDER BY category_id;  -- 或删除ORDER BY
  • 避免非必要的DISTINCTUNION
    检查是否可以重写逻辑,减少中间结果集。

(2) 添加合适的索引
  • GROUP BY/ORDER BY/JOIN列创建复合索引
    使操作能直接通过索引完成,避免临时表。
    示例
  -- 原查询(无索引导致临时表)
  SELECT user_id, COUNT(*) 
  FROM logs 
  GROUP BY user_id;

  -- 添加索引优化
  ALTER TABLE logs ADD INDEX idx_user_id (user_id);
  • 使用覆盖索引(Covering Index)
    索引包含所有查询字段,避免回表。
    示例
  -- 查询需要user_id和amount字段
  SELECT user_id, SUM(amount) 
  FROM orders 
  GROUP BY user_id;

  -- 创建覆盖索引
  ALTER TABLE orders ADD INDEX idx_user_amount (user_id, amount);

(3) 调整数据库配置
  • 增大临时表内存阈值
    通过调整tmp_table_sizemax_heap_table_size,使临时表尽量存储在内存中。
    配置建议
  tmp_table_size = 64M
  max_heap_table_size = 64M
  • 监控临时表使用情况
    执行SHOW GLOBAL STATUS LIKE 'Created_tmp_%tables';,若Created_tmp_disk_tables值过高,需优化查询或增加内存。

(4) 重写复杂子查询或JOIN
  • 将子查询转换为JOIN
    减少中间临时表的生成。
    示例
  -- 原查询(子查询导致临时表)
  SELECT * 
  FROM users 
  WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);

  -- 优化为JOIN
  SELECT users.* 
  FROM users 
  JOIN orders ON users.id = orders.user_id 
  WHERE orders.amount > 100 
  GROUP BY users.id;  -- 去重
  • 使用派生表(Derived Table)优化
    结合业务逻辑,分步处理中间结果。

(5) 减少临时表的数据量
  • 仅选择必要字段
    避免SELECT *,减少临时表存储的数据量。
    示例
  -- 原查询
  SELECT * 
  FROM orders 
  GROUP BY user_id;

  -- 优化后:仅选择必要字段
  SELECT user_id, COUNT(*) 
  FROM orders 
  GROUP BY user_id;

3. 验证优化效果
  • 重新执行EXPLAIN
    检查Extra字段是否消除Using temporary
  • 观察查询性能
    通过SHOW PROFILES或监控工具确认执行时间减少。

4. 面试回答示例

回答思路

  1. 定位问题:通过EXPLAIN发现Using temporary,说明查询需创建临时表。
  2. 分析原因:常见于未索引的GROUP BY/ORDER BY、复杂子查询或JOIN
  3. 优化步骤
    • 优化查询结构,统一GROUP BYORDER BY列。
    • 添加复合索引或覆盖索引。
    • 调整tmp_table_size提升内存利用率。
    • 重写子查询为JOIN,减少中间结果集。
  4. 验证效果:重新EXPLAIN并监控性能。

示例回答
“当发现EXPLAINExtra字段有Using temporary时,我会首先检查查询中是否有GROUP BYORDER BY未用索引,或者复杂子查询。例如,若GROUP BYORDER BY列不一致,可能需创建临时表排序。优化方法包括:

  1. 为这些列添加复合索引,使排序和分组直接通过索引完成;
  2. 调整查询逻辑,统一GROUP BYORDER BY列;
  3. 若无法避免临时表,增大tmp_table_size让更多临时表存于内存。
    同时,重写子查询为JOIN或减少SELECT字段也能有效降低临时表的数据量。”

总结

优化临时表的核心是减少中间结果集的数据量避免磁盘I/O。通过索引优化、查询重构和配置调整,可显著提升性能。需结合EXPLAIN结果和实际业务场景灵活选择策略。

redis的事务原理,内存机制

Redis 的事务机制提供了一种将多个命令打包、按顺序一次性执行的机制,但与传统数据库(如 MySQL)的事务相比,Redis 的事务不支持回滚(Rollback),且设计理念更偏向于简单和高效。以下是 Redis 事务的核心机制和关键点:


1. 事务的基本流程

Redis 事务通过以下三个命令实现:

  • MULTI:标记事务开始,后续命令会按顺序进入队列(但不会立即执行)。
  • EXEC:执行事务队列中的所有命令。
  • DISCARD:取消事务,清空队列中的命令。

示例

MULTI          -- 开始事务
SET key1 "A"   -- 命令入队
SET key2 "B"   -- 命令入队
EXEC           -- 执行事务

2. 事务的原子性

  • 执行原子性:所有命令在 EXEC 时一次性按顺序执行,不会被其他客户端命令打断。
  • 错误处理
    语法错误(如命令不存在):在 EXEC 执行前会直接报错,事务不会执行。
    运行时错误(如对字符串执行 INCR):错误命令会失败,但后续命令继续执行,不会回滚

示例

MULTI
SET key1 "Hello"
INCR key1      -- 对字符串执行 INCR 会失败
SET key2 "World"
EXEC
-- 结果:key1 被设置为 "Hello",key2 被设置为 "World",INCR 失败但事务继续执行。

3. 乐观锁(WATCH)

  • WATCH 命令:用于监控一个或多个键,如果在事务执行前这些键被其他客户端修改,则事务会被取消(类似乐观锁)。
  • UNWATCH:取消对所有键的监控。

使用场景:确保事务执行期间监控的键未被修改(如余额扣减)。

WATCH balance         -- 监控 balance 键
MULTI
DECRBY balance 100    -- 扣减余额
INCRBY debt 100       -- 增加债务
EXEC                  -- 如果 balance 在 WATCH 后被其他客户端修改,事务失败

4. 事务的特点

  1. 无隔离性:事务中的命令在 EXEC 前不会实际执行,但其他客户端可能修改事务中涉及的键。
  2. 批量执行:适合需要原子性批量执行多个命令的场景。
  3. 不支持回滚:设计哲学是“保持简单高效”,错误由开发者自行处理。

5. 适用场景

  • 批量执行命令(如初始化数据、批量更新)。
  • 结合 WATCH 实现简单并发控制(如库存扣减、抢购)。

6. 与 Pipeline 的区别

  • 事务:保证原子性,所有命令一起执行。
  • Pipeline:将多个命令打包发送以减少网络开销,但不保证原子性。

总结:

Redis 的事务机制通过 MULTI/EXEC 实现命令的批量原子执行,但缺乏回滚功能,需结合 WATCH 实现乐观锁控制。它适合简单场景下的批量操作,对复杂事务需依赖应用层逻辑补偿。


豹趣科技

工程化问题的看法

在协同工作的情况下,编码的可维护性,可靠性,可读性。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇