需要注意的点:
- 一定要提前准备面试!技术面试不同于编程,编程厉害不代表技术面试就一定能过。
- 一定不要对面试抱有侥幸心理。打铁还需自身硬!千万不要觉得自己看几篇面经,看几篇面试题解析就能通过面试了。一定要静下心来深入学习!
- 建议大学生尽可能早一点以求职为导向来学习的。这样更有针对性,并且可以大概率减少自己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。 但是,不要把“以求职为导向学习”理解为“我就不用学课堂上那些计算机基础课程了”!
- 手撕算法是当下技术面试的标配,尽早准备!
- 你的项目经历涉及到的知识点,有水平的面试官都是会根据你的项目经历来问的。
要掌握的问题:
内容 | |
事务 | |
一致性 | |
如何恢复数据 |
爆肝整理的Golang面试题,拿走不谢
MySQL常见面试题总结 | JavaGuide(Java面试 + 学习指南)
MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余(收藏系列) - 简书
Java面试常见问题总结(2023最新版),附参考答案! - 掘金
代码随想录
面试必备(背)--Go语言八股文系列!-腾讯云开发者社区-腾讯云
项目:
流程编排服务:
背景:随着公司发展,海外业务线独立拆分, 需要建立海外的审核体系。保证公司对外的内容是健康绿色的。海外的整套审核体系经历了由0-1, 1-10的过程。(业务送审、特征获取、决策执行、分流送人人审、人审审核、处置)。我这边主要负责的就是海外审核业务中的机审服务。
机审这个业务线的这套业务逻辑其实就是:获取实时计算模型结果、根据结果做决策、对于高风险的内容需要送入人审,人审在做最后的决定。
流程服务主要的能力:特征、模型、流程、规则的管理。
-
提供了审核的流程编排与节点执行能力,能够支持任务按序执行、分支选择、上下文传递、异常处理、节点并发执行以及并行迭代等流程控制能力。
-
管理审核相关特征信息,以标准化、配置化的方式输出特征数据。提供一套完成用户管理特征能力。
-
标准化的规则表达方式组织内容安全领域内的审核规则。//code的编码去了解下
-
普通、分流、跳转(条件和Action)
-
随着流量的增长,服务的效率、稳定性这块也有比较高的要求。比如直播对我们审核要求分钟级别审出SLA是要求四个9. (稳定性:配置上线、流量问题(分流隔离、流量突增、流程耗时过高)、出问题后如何处理 效率: 标准化、DAG、迁移)
效果:10万+的流量,基本上所有场景都在分钟级别处理完成。基本上没啥大的事故
服务的设计: 模版模式 + 代理模式: base_handler 、 real_handler 、 handler
规则做的事情:比如:当直播画面中有武器,并且血液识别Gandalf分数大于20,则标定风险结论为暴力;当风险结论为武器时,若主播不是官媒号,则直接送人武器专审队列。
hawk VS flink
hawk关注流程,就是业务执行逻辑,一条运行业务逻辑。强绑定审核的。动作是固定的,不需要拆解。对于某些模型运行应该可以拆分为多个计算。
flink关注的计算,讲一个完成的计算,拆成多个多个计算,提高效率。比如我们常说的统计问题
回答问题的基本思路:
先回答背景、为什么会这么做,然后在回答其存在的问题。
过年假期:热门一百道题要在刷一次、重点思路。go语言写法要熟悉。
在看一遍书、在总结下。
项目这块还需要在加强。
注意项:coding前要认真审题,题目清晰了在动手。
基础八股文:
知识点 | 解释 |
死锁 | 互斥、请求和保持、不可剥夺、循环等待 经典:A付钱,B收钱 |
上下文 | cpu在从A进程/线程切换到B进程/线程需要重新加载对应的信息 |
可重入锁 | 可重入锁,也叫做 递归锁,从名字上理解,字面意思就是再进入的锁,重入性是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,首先他需要具备两个条件:线程再次获取锁:所需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功锁的最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其它线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前线程被重复获取的次数,而被释放时,计数自减,当计数为0时表示锁已经成功释放。 好处:一定程度上避免死锁 |
cpu/内存问题 | 1. cpu耗时的的排查思路:上下文切换、cpu执行耗时 上下文切换: 过多的资源竞争(代码逻辑合理、并发太高等等) cpu计算过高: 单次计算消耗资源过大(大对象序列化)、并发量是不是太高 2. 内存太高,OOM出现: 创建对象过多:并发、或者代码一次创建对象太多 创建对象太大:IO一般会比较大、或者有突刺 创建的对象没有回收:写法是那个对象是否没有被引用 |
算法基础(按照分类总结下,把150道题目的思路整下):
知识点 | 解释 |
数组相关题目 | 1. 双指针思路:一个指针指向老的,一个指针指向新的 丑数问题 两数之和/三数之和/四数之和(先排序后操作) 乘最多的水 删除数组重复项二:. - 力扣(LeetCode) 2. 动态规划:当前状态是有之前状态做不同选择得到的 买卖股票的最佳时机:. - 力扣(LeetCode) 3. 前缀和:利用前缀和做一些运算 除自身以外数组的乘积 :. - 力扣(LeetCode)除自身以外数组的乘积 : 4. 左右结果合并:将从左到右运行结果和从右到左结果merge 分发糖果、接雨水。 5. 滑动窗口: 一般是取一定范围的操作,关键词“子串” 长度最小的子串:. - 力扣(LeetCode) 6. 二维数组:基本上是模拟、hash节约 7. 利用数组的下标,进行排序(数字范围在固定范围内的) |
字符串 | 1. 字典树:root是空,每个字母一个节点,用来做匹配 (在写下) 最长公共子序列:. - 力扣(LeetCode) 2. 双指针:操作对应字符串的某些位置。p1是指向之前的,p2是新的位置 字符串空格替换20% 3. hash表:一般是用来做统计,统计对应字母的数字。或者是快速查找数据 最长的连续序列:. - 力扣(LeetCode) 4. 字符串归类,用于同一类别(某种题目设计的规律)统计 5. 动态规划问题:两个字符串的匹配、转化问题 6. 字符串比较大小: AB - BA 对比大小 7. 字母为26位长度的数组。ascall码是256位长度字符,本质上为map |
栈 | 关键字:括号匹配、表达式、递归 1. 表达值,通过表达式获取结果。特点内结果会被外结果使用. 波兰表达式:. - 力扣(LeetCode) 3. 括号问题,需要左括号和右括号匹配 有效括号:. - 力扣(LeetCode) 4. 多个栈解决问题,同时弹出、或者接力 5. 单调栈,永远是递增的栈。如题目每日温度、 |
链表 | 1. 双指针:快慢指针定位对应位置、对付多条链表 2. 头插入法: 一般会影响到头节点,需要单独的头节点, 反转链表 |
二叉树 | 关键字:遍历数据 1. 深度遍布、层序遍历 2. 图的问题一般采用深度遍历、层序遍历 3. 最小生成树,一般是寻找最小路径 如leetcode 1168题 |
回溯 | 遍历出所有的解。在数的基础上加了一个退回去的操作 经典题目:数字的全排序,打印1到n的数 解决迷宫类的问题 基本特点:多步骤问题、一个步骤后,后面出现多个步骤。 |
回溯VS动态规划 | 1. 看是否有最优的解还是没有最优解。 2. 动态规划问题,子问题会有重复 |
二分查找 | 1. 寻找有序、峰值的点 2. 二维数组查找,二维数组要排好顺序 |
区间合并 | 1. 先排序,然后前区域不断向后扩散 |
动态规划 | 1. 0 -1 背包问题,在总结下,股票、字符串匹配; dp[i][j] = max(dp[i-1][j], dp[i-1][j - w(i)] + v(i)]) // 最小编辑距离、最长回文、最长公共子序列 2. 一般是解决 最优/最大/最小等最值问题。一定要抽象出对应的状态方程 经典题目 字符串匹配:. - 力扣(LeetCode) |
常用思路 | a的n次方: n为偶数:a的n/2 * a的n/2 n为奇数: a的(n -1)* a的(n-1) * n n与n-1位相与,相当于将最左一位的1变成0 |
边界问题 | 1. 大数问题 2. 正负数 3. 0为底数、指数为负数 4. float和doblue不能直接比较,有精度误差 |
括号问题,思路如果是递归的思路,可以考虑用栈。 | 如 "e3[d]3[b2[c]]"的展开 |
基础算法 | 1. 快排、堆排、归并、单例、消费者/生产者、全排 |
常见写法 | |
go相关的八股文:
知识点 | 解释 |
读写并发控制 | 1. Channel 进行安全读写共享变量,简单的少量协程间的通信。类比于消息中间件 1.1 无缓冲,类似同步机制,有缓存,当缓冲满时候接受阻塞。 2. Mutex 并发锁:sync.mutex 3. atomic包:无锁技术,本质上是CAS 3.1. 信号量 pv原语来实现 3.2: once语句,其实就是单例,多次只会执行一次 3.3: cond 条件语句,某些情况下唤起一次 4. context,Go内置的一种接口类型,其实就是全局变量 |
GMP模式 | Golang内部有三个对象: P对象(processor) 代表上下文(或者可以认为是cpu),M(work thread)代表工作线程,G对象(goroutine) Golang是为并发而生的语言,Go语言是为数不多的在语言层面实现并发的语言; G理论上是无限,P 的数量一般建议是逻辑 CPU 数量的 2 倍,M 的数据默认启动的时候是 10000, |
go的内存模型 | 程序在内存上被分为堆区、栈区(函数的参数值、返回值、局部变量)、全局数据区、代码段、数据区五个部分 1. 在C/Go语言中局部变量, 只有执行了才会分配存储空间, 只要离开作用域就会自动释放, C语言的局部变量存储在栈区 2. C/Go语言中全局变量, 只要程序一启动就会分配存储空间, 只有程序关闭才会释放存储空间, C语言的全局变量存储在静态区(数据区) |
堆和栈 | 栈(Stack)是一种线性结构,它的内存空间是连续的,类似于一堆盘子叠在一起,后进先出。每个线程都会有自己的栈,用来存储函数调用时的临时数据,如局部变量、参数、返回值等。 堆(Heap)是一种动态分配内存的机制,它的内存空间是不连续的,类似于一大堆散装货物。在堆中分配的内存需要手动申请和释放,因此需要开发者自己管理。堆的内存空间通常用于存储比较大的数据结构,如动态数组、哈希表、二叉树等。 |
内存回收 | 白-->灰--->黑,直到灰为空结束 1. 强三色不变式:不允许黑色对象引用白色对象 2. 弱三色不变式:黑色对象可以引用白色,白色对象存在其他灰色对象对他的引用,或者他的链路上存在灰色对象 屏障机制分为插入屏障和删除屏障,插入屏障实现的是强三色不变式,删除屏障则实现了弱三色不变式 删除屏障:对象被删除时触发的机制。如果灰色对象引用的白色对象被删除时,那么白色对象会被标记为灰色。 插入屏障:对象被引用时触发的机制,当白色对象被黑色对象引用时,白色对象被标记为灰色(栈上对象无插入屏障)。 1.8 三色标记 + 混合写屏障 GC 开始时将栈上可达对象全部标记为黑色(不需要二次扫描,无需 STW) GC 期间,任何栈上创建的新对象均为黑色 被删除引用的对象标记为灰色 被添加引用的对象标记为灰色 |
root节点 | 根对象在垃圾回收的术语中又叫做根集合,它是垃圾回收器在标记过程时最先检查的对象,包括: 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。 执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含栈上的变量及指向分配的堆内存区块的指针。 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块 |
tcmalloc | 1. TCMalloc 会给每一个线程分配一个属于线程本地的缓存(Thread Cache) 小对象使用已经分配好的簇,大对象 1. 分配规则按照 8, 16, 32, 48, 64, 80这样子来。注意到,这里并不是简单地使用2的幂级数,因为按照2的幂级数,内存碎片会相当严重. 2. 也就是多个连续的 Page 会组成一个 Span,大的对象直接分配 Span,小的对象从 Span 中分配 |
gc过程 | 写入屏障的流程:程序开始,全部标记为白色, 1)所有的对象放到白色集合, 2)遍历一次根节点,得到灰色节点, 3)遍历灰色节点,将可达的对象,从白色标记灰色,遍历之后的灰色标记成黑色,4)由于并发特性,此刻外界向在堆中的对象发生添加对象,以及在栈中的对象添加对象,在堆中的对象会触发插入屏障机制,栈中的对象不触发, 5)由于堆中对象插入屏障,则会把堆中黑色对象添加的白色对象改成灰色,栈中的黑色对象添加的白色对象依然是白色, 6)循环第 5 步,直到没有灰色节点, 7)在准备回收白色前,重新遍历扫描一次栈空间,加上 STW 暂停保护栈,防止外界干扰(有新的白色会被添加成黑色)在 STW 中,将栈中的对象一次三色标记,直到没有灰色, 8)停止 STW,清除白色。至于删除写屏障,则是遍历灰色节点的时候出现可达的节点被删除,这个时候触发删除写屏障,这个可达的被删除的节点也是灰色,等循环三色标记之后,直到没有灰色节点,然后清理白色,删除写屏障会造成一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮 GC 中被清理掉。 |
context | 1. context 是 go 中控制协程的一种比较方便的方式,goroutine 的上下文。所有基于这个 Context 或者衍生的子 Context 都会收到通知,这时就可以进行清理操作了,最终释放 goroutine,这就优雅的解决了 goroutine 启动后不可控的问题。 2. Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} 当作“毒丸” 3. 设置截止日期、同步信号,传递请求相关值的结构体 查下:context使用 |
make和new区别 | make和new都是:在堆上给变量分配内存 1. new 分配的空间被清零。make 分配空间后,会进行初始化 2. new返回指针,而make返回的是类型 |
数组与切片 | 数组是定长,slice是一个数组的指针 |
for range | 参数a、b只有一份,每次都是copy过去使用 |
defer | defer延迟函数,释放资源,收尾工作;如释放锁,关闭文件,关闭链接;捕获panic defer 语句都对应一个_defer 实例,多个实例使用指针连接起来形成一个单连表,保存在 gotoutine 数据结构中,每次插入_defer 实例,均插入到链表的头部,函数结束再一次从头部取出,从而形成后进先出的效果。 |
rune | go的String是通过byte数组实现的,rune等同于int32 |
反射 | 反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力或动态知道给定数据对象的类型和结构,并有机会修改它。 |
slice | Go 的 slice 底层数据结构是由一个 array 指针指向底层数组,len 表示切片长度,cap 表示切片容量。slice 的主要实现是扩容。对于 append 向 slice 添加元素时,假如 slice 容量够用,则追加新元素进去,slice.len++,返回原来的 slice。当原容量不够,则 slice 先扩容,扩容之后 slice 得到新的 slice,将元素追加进新的 slice,slice.len++,返回新的 slice。对于切片的扩容规则:当切片比较小时(容量小于 1024),则采用较大的扩容倍速进行扩容(新的扩容会是原来的 2 倍),避免频繁扩容,从而减少内存分配的次数和数据拷贝的代价。当切片较大的时(原来的 slice 的容量大于或者等于 1024),采用较小的扩容倍速(新的扩容将扩大大于或者等于原来 1.25 倍),主要避免空间浪费,网上其实很多总结的是 1.25 倍,那是在不考虑内存对齐的情况下,实际上还要考虑内存对齐,扩容是大于或者等于 1.25 倍。 |
map删除key后会不会被回收 | 1. 当你使用 2.虽然 |
map | golang 中 map 是一个 kv 对集合。底层使用 hash table,用链表来解决冲突 ,出现冲突时,不是每一个 key 都申请一个结构通过链表串起来,而是以 bmap 为最小粒度挂载,一个 bmap 可以放 8 个 kv。在哈希函数的选择上,会在程序启动时,检测 cpu 是否支持 aes,如果支持,则使用 aes hash,否则使用 memhash。每个 map 的底层结构是 hmap,是有若干个结构为 bmap 的 bucket 组成的数组。每个 bucket 底层都采用链表结构。 扩容: 1)装载因子超过阈值,源码里定义的阈值是 6.5。 2)overflow 的 bucket 数量过多 map 的 bucket 定位和 key 的定位高八位用于定位 bucket,低八位用于定位 key,快速试错后再进行完整对比 |
mutex | 原理: state字段,利用atomic包实现原子操作。atomic包底层是使用CPU指令保证原子性。 1. 当前的mutex只有一个goruntine来获取,那么没有竞争,直接返回 2.新的goruntine进来,mutex处于空闲状态,将参与竞争。新来的 goroutine 有先天的优势,它们正在 CPU 中运行,可能它们的数量还不少,所以,在高并发情况下,被唤醒的 waiter 可能比较悲剧地获取不到锁,这时,它会被插入到队列的前面。如果 waiter 获取不到锁的时间超过阈值 1 毫秒,那么,这个 Mutex 就进入到了饥饿模式。 3. 在饥饿模式下,Mutex 的拥有者将直接把锁交给队列最前面的 waiter。新来的 goroutine 不会尝试获取锁,即使看起来锁没有被持有,它也不会去抢,也不会 spin(自旋),它会乖乖地加入到等待队列的尾部。 如果拥有 Mutex 的 waiter 发现下面两种情况的其中之一,它就会把这个 Mutex 转换成正常模式:
|
内存泄漏 | 1. 如果 goroutine 在执行时被阻塞而无法退出,就会导致 goroutine 的内存泄漏 2. 互斥锁未释放或者造成死锁会造成内存泄漏 3. 字符串的截取引发临时性的内存泄漏 4.切片截取引起子切片内存泄漏 5. 数组传入数据 |
内存逃逸 | 编译器会根据变量是否被外部引用来决定是否逃逸: 如果函数外部没有引用,则优先放到栈中; 如果函数外部存在引用,则必定放到堆中; 如果栈上放不开,则必定放到堆上; 常见场景: 指针逃逸、栈空间不足逃逸、动态类型逃逸(interface)、闭包引用对象逃逸 内存逃逸通常是由于以下情况引起的: 变量的生命周期超出作用域:在函数内部声明的变量,如果在函数返回后仍然被引用,就会导致内存逃逸。这些变量将被分配到堆上,以确保它们在函数返回后仍然可用。 引用外部变量:如果函数内部引用了外部作用域的变量,这也可能导致内存逃逸。编译器无法确定这些外部变量的生命周期,因此它们可能会被分配到堆上。 使用闭包:在 Go 中,闭包(函数值)可以捕获外部变量,这些变量的生命周期可能超出了闭包本身的生命周期。这导致了内存逃逸。 |
内存模型(内存可见性,有序性) | 由于 CPU 指令重排和多级 Cache 的存在,保证多核访问同一个变量这件事儿变得非常复杂。毕竟,不同 CPU 架构(x86/amd64、ARM、Power 等)的处理方式也不一样,再加上编译器的优化也可能对指令进行重排,所以编程语言需要一个规范,来明确多线程同时访问同一个变量的可见性和顺序。在编程语言中,这个规范被叫做内存模型。 golang 提供了关于 package init,goroutine create,goroutine destroy,channel communication,locks,once 五个方面的 happens-before 规则,其中 channel 和 locks 涉及到的最多,所以 channel,locks 也是最常见保证内存正确可见性的手段 demo: 一个元素的 send 操作先于这个元素 receive 调用完成(结果返回) no buffer 的 channel 的 receive 操作可见于 send 元素操作完成。 (n < m)第 n 次调用 |
基本写法 | 1. string 转化为 数组: bytes := []byte(s) 2. int的最大值:value := 2e32 - 1 3. compare写法:sort.Slice(slist, func(i, j int) bool { return slist[i] < slist[j] }) 4. |
java相关的八股文:
知识点 | 解释 |
比较重要 | 1. string 、map 、list、 gc、线程池、 锁、spring(?) |
基本写法 | 1. 最大值:Integer.MIN_VALUE/MAX_VALUE |
线程状态 | 1. 开始、就绪、运行、等待(wait/sleep/join)、结束 |
线程实现 | 继承Thread、实现Runnable、 实现Callable(Feature) 、 线程池方式。 归根结底都是实现Runnable。 |
wait/sleep | 1. sleep 属于Tread,自动唤醒,不释放锁、 2. wait属于Object。手动唤醒、释放锁、必须要有锁。 wait的底层逻辑是:将持有锁的对象从Owner丢到waitSet中去。在修改synchronized的ObjectMoniter对象。 |
终止线程 | Stop(强制)、interrput(线程内部中断标记位)、共享变量(一个信号) |
并发编程(三个大特性) | 1. 原子性 : 锁(synchronized/Lock)、CAS、 theadLoacal()。 2. 可见行:线程内存与主内存, violate、 锁、 final。 3. 有序性:指令重排。violate避免指令重排。 |
CAS的问题 | 1. CPU浪费 2. ABA 问题,可以用版本问题 3. 避免用户态和内核态切换 |
四种引用 | 1. 强引用,处于可达状态,不会被回收。new 2. 软引用,系统内存不足时,会被回收。 softReffer 3. 弱引用, gc时候,直接回收 theadlocal ThreadLocal(副本引用), key为虚引用, 使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏。 在每次调用ThreadLocal的get、set、remove方法时都会执行,即ThreadLocal内部已经帮我们做了对key为null的Entry的清理工作。 4. 虚引用, 跟踪垃圾回收对象 |
锁分类 | 1. 可/非重入 2. 乐观锁、悲观锁 3. 公平锁和非公平锁 |
synchronized原理 | 1. 无锁、膨胀、升级 |
AQS | 1. 提供State(violate、CAS), 是否被线程持有。 2. 双向链表 没有锁的对象插入到对应的列表。 3. conditionObject 下双向链表,维护挂起的队列。Synchronized是维护一个waitSet队列。 注意: 唤醒节点为什么是从后往前:Add和delete 都是先动pre指针后动next。 |
GC | 1.堆、程序计数器、本地方法栈、虚拟机栈、方法区 2. 将内存分成了三大块:年青代(Young Genaration),老年代(Old Generation),永久代(Permanent Generation) 年轻代2个, 老年代,随着复制的挪移次数,移动到老年代。 |
基础类型 | boolen:1bit、byte:1字节、short: 2字节、char:2字节、int:4字节、float:4字节、long: 8字节、double:8字节 |
spring中用到的设计模式 | 1. 工厂模式:实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值。 2.Spring依赖注入Bean实例默认是单例的。 getSingleton()过程图 3. 代理模式,AOP实现就是采用代码模式 4. 观察者模式:实现类ApplicationContextEvent表示ApplicaitonContext的容器事件 5. 策略模式:source 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。 6. 模版方法模式:父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。jdbcTemplate 7. 享元模式:Integer 和String都有缓存池,创建时候都可以先去缓存池中查询先 |
java代理模式 | 1. 静态代理,代理类需要实现类中所有的方法 2. java代理:代理对象通过反射invoke调用方法。目标对象得实现接口 3. Cglib代理可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。 JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类,并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。 |
数据类型大小 | string 是length(), 数组是length, list是size() |
String、StringBuffer、StringBuilder | 1. String是final,StringBuffer是byte数组, StringBuilder是线程安全。 final的好处: 2. 维护一个数组,每次都扩充内部数据,最后转化。 |
string创建对象问题 | string是放在常量池(堆里面), 对象相当于一个索引。一般是3个 |
==和equal | 一个是操作符,一个是方法。equal在没重写之前和==一样 ==比较的是 |
装箱的缓存机制 | 因为 Integer、Long 这种包装类有缓存机制,valueOf 方法会从缓存中取值,如果命中缓存,会减少资源的开销,parseXXX 方法没有这个机制 |
Exception和Error | Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。 Error 是指在正常情况下,不大可能出现的情况,绝大部分的Error 都会导致程序(比如JVM 自身)处于非正常的、不可恢复状态。 |
Spring | ioc、aop、 框架结合(mvc、mybatic) |
spring常用注解 | 1.@Autowired只按照byType 注入;@Resource默认按byName自动注入,也提供按照byType 注入 @Autowired+@Qualifier == @Resource,项目中如果有多个类,用@Resource注解 2.@Component 管理组件的通用形式 @Service对应的是业务层@Controller对应表现层@Repository对应数据访问层(持久层) 3. |
spring bean 生命周期、循环依赖问题 | 实例化-->属性赋值-->初始化--->销毁。循环依赖:三级缓存:第一级:singleObject 可使用的。第二级:earlySingleObject 实例化的对象,未填充属性,第三级:工厂存储。三个缓存都是互斥的,只会保持bean在一个缓存中。三级切合单一职责,符合生命周期,解决代理封装 |
spring AOP IOC | 1.IOC(Inversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由给Spring框架来管理。 2.AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。面向切面编程, 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术 |
wait(), notify()和 notifyAll()被定义在 Object 类里 | Java中任意对象都可以为锁,但java中没有可以锁任意对象的锁,所以定义在object。定义在thread,thread中可以持有多个锁,可以实现,但是复杂 |
public、private、protected、default | private<default<protected<public(本身<本身+包<本身+包+子<所有) |
面向对象、面向过程 | 面向过程:具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。面向对象:象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。 |
类的加载顺序 | 父类的静态->子类的静态->父类的初始化块->父类的构造方法->子类的初始化块->子类的构造方法 |
面向对象编程三大特性 | 封装 继承 多态(继承+接口,在运行时候才知道具体做什么) |
抽象类与接口 | 抽象类:继承,子类型必须和父类型一致。 接口: 具不具备的问题 相同点:接口和抽象类都不能实例化、都位于继承的顶端,用于被其他实现或继承、都包含抽象方法,其子类都必须覆写这些抽象方法 不同点:申明:抽象类使用abstract关键字声明、接口使用interface关键字声明 实现:子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现、子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 多继承:一个类最多只能继承一个抽象类、一个类可以实现多个接口 访问修饰符:抽象类中的方法可以是任意访问修饰符、接口方法默认修饰符是public 构造器:抽象类可以有构造器、接口不能有构造器 字段声明:抽象类的字段声明可以是任意的、接口的字段默认都是 static 和 final 的 |
Java创建方式 | new、clone、Class.forName、反序列化、sun.misc.Unsafe中提供allocateInstance |
Java反射方式 | 1.建立对象stu.getClass() 2.路径-相对路径Class.forName("fanshe.Student")3.类名Student.class |
hashMap put方法 | 为空先扩容、存在就覆盖、添加先链表、红黑树 |
currentHashMap | 1. init, 通过CAS判断sizeCtl值,控制只有一个线程能初始化。其他都yield 2. 扩容,1. 确认是否扩容、建立新的数组, 2. 各个线程领取一段扩容,之前位置设置被迁移标识。 3 最后一个线程扫描是否全迁移完成。 3. find: 情况1: 不存在返回空 情况2: 扩容中,完成后在新数组中找。 情况3 红黑树平衡,等写数据完成后在查询 情况4: 查询链表/红黑树 4. 计数器:并发数小:baseCount,出现了竞争,初始化counterCells, 使用counterCells 并发大:数组counterCells使用 |
线程创建方式 | thread、runnable、callable、Executors |
线程区别 | 等待(wait)/阻塞(sleep):等待会释放锁/阻塞不会 yield方法会临时暂停当前正在执行的线程,让当前线程转化为可执行线程,其他同等级线程有机会获取对应的cpu。 |
ThreadLocal造成内存泄漏 | key为thread标示、且为弱引用,在GC时被回收。entry在set、get、remove时候被移除。(用完就清理) |
线程池优点 | 1.重用线程,降低创建、销毁频率,提高效率2.可管理,附加功能定期执行、优先级等 |
线程池的拒绝策略 | ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务 |
增长策略和收缩策略 | 1. 增长策略。默认情况下,线程池是根据任务先创建足够核心线程数的线程去执行任务,当核心线程满了时将任务放入等待队列。待队列满了的时候,继续创建新线程执行任务直到到达最大线程数停止。再有新任务的话,那就只能执行拒绝策略或是抛出异常。 2. 收缩策略。当线程池线程数量大于核心线程数 && 当前有空闲线程 && 空闲线程的空闲时间大于 keepAliveTime 时,会对该空闲线程进行回收,直到线程数量等于核心线程数为止。 |
四种线程池大小 | Executors.newCachedThreadPool():这是具有缓冲功能的线程池,系统根据需要创建线程。线程会被缓冲到线程池中,如果线程池大小超过了处理任务所需的线程,线程池就会回收线程,当任务增加时,线程池可以增加线程来处理任务,线程池不会对线程的大小进行限制,线程池的大小依赖于操作系统。 最大线程数很大 Executors.newFixedThreadPool(3):创建具有一个可重用的,有固定数量的线程池。每次提交一个任务就提交一个线程,直到线程达到线程池大小,就不会再创建新线程了,线程池的大小达到最大后就稳定不变了,再提交任务就在队列中等待,如果一个线程异常终止,则会创建新的线程。 核心线程数等于最大线程数 Executors.newSingleThreadExecutor():创建只有一个线程的线程池,按照提交顺序执行,跟上个数量为1的是一样。 核心线程数和最大线程数为1 Executors.newScheduledThreadPool:创建一个线程池,大小可以设置。此线程支持定时以及周期性的执行任务。 核心线程数、最大工作线程数、工作队列 |
CycliBarriar 和 CountdownLatch 有什么区别 | 1.CycliBarriar一组等待,CountdownLatch一个等待多个。2 CycliBarriar采用wait,CountdownLatch采用CAS |
类分配内存 | 指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。 |
基础变量存储位置 | 在方法中,存在JVM栈的方法栈,在全局变量时,会在堆中创建一个对象 |
双亲委派机制 | 避免重复加载、安全性提高 缺陷:在BootstrapClassLoader或ExtClassLoader加载的类A中如果使用到AppClassLoader类加载器加载的类B,由于双亲委托机制不能向下委托,那可以在类A中通过线上线程上下文类加载器获得AppClassLoader,从而去加载类B,这不是委托,说白了这是作弊,也是JVM为了解决双亲委托机制的缺陷不得已的操作! |
synchronized和Volatile保障可见性和原子 | synchronized(可见与原子):对象加锁、将主内存数据拷贝到工作内存,执行完成,同步到主内存,在释放锁。单线程模式 Volatile(可见):会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值 |
synchronized修饰静态和非静态 | https://www.cnblogs.com/xt-Corrine/p/10590625.html 修饰方法:使用的是对象锁,同一个对象,使用会互斥,不同对象不会。 修饰静态方法:使用的是类锁,用类或者静态对象调用都会产生互斥,但是用一个对象分别调用静态/非静态不会互斥 |
为什么是某些值 | hash槽的数量: 一直hash环的数量: hashmap参数转化红黑树为8:TreeNode是普通node的两倍,链表长度符合柏松分布,当长度为 8 的时候,概率仅为 0.00000006。 参考:https://blog.csdn.net/xiewenfeng520/article/details/107119970 hashmap参数0.75扩容因子:哈希冲突(时间)和空间利用率的一个折中,提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小 |
hashmap 1.8 与1.7 | 1.1.8采用尾插,1.7采用头插。头插并发容易出现循环依赖。 2.扩容时计算扩容后的位置:1.7:hash值 & length-1 1.8(高低位,加上原本的旧长度)1.8 扩容前的原始位置+扩容的大小值 3.1.7:数组+单链表 1.8 数组+链表+红黑树 4.1.7 先扩容在插入(存在hash冲突在扩容) 1.8 先插入在扩容(因为你插入这个节点的时候有可能是普通链表节点,也有可能是红黑树节点。) 参考:全面分析Hashmap在JDK1.7和JDK1.8有哪些区别_杰克说互联网的博客-CSDN博客 尾扩树插 |
currenthashmap 1.8Vs 1.7 | 1. 数据结构: 1.7:数组+单链表 1.8 数组+链表+红黑树 2. 锁机制:1.7是分段锁、1.8是CAS加synchronized 3. 计数:1.7是单个计数, 1.8是分成多个计数,然后汇总 |
list问题 | elementData 为什么是transien:ArrayList的设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组 |
reentrantlock | 公平和非公平:公平:先检查CLH每个节点是否有前驱节点。非公平:直接检查锁的状态。 中断:采用底层lockSupport.park().park.park是响应中断的。进入中断状态 |
ThreadLocalMap | 注意点:remove后会进行rehash 线程安全:如果threadlocal.get获取的线程变量副本,只在当前线程中使用,那么是线程安全的;如果对其他线程暴露,不一定是线程安全的。 启发式清理和探测式清理:当前 |
GC root对象 | 1.方法区(元空间)的静态类属性、常量 2.本地方法区JNI的对象 3虚拟机栈的常量 二方一长 |
CMS | 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ; 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。 |
jdk代理和CGLIB代理 | 1.jdk(invokeHandle) 拦截器+反射,必须实现方法 2.cglib(methodInteceptor ) 修改字节码创建,可被重写 |
checkException与unCheckException | unCheckException 是未检查到异常,包括error与runningException。如空指针、outOfmenory checkException 是检查型异常,在编辑时候发现、如class not found |
锁升级 | 无锁、偏向锁(上次是你拿的,这次还是你)、轻量锁(CAS)、重量级锁 |
CountDownLatch和CyclicBarrier和Semaphore | CountDownLatch 一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行; CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行; Semaphore翻译成字面意思为 信号量,Semaphore 可以同时让多个线程同时访问共享资源,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。 |
spring 传播行为 | 事务传播行为:多个事务方法相互调用时,事务如何在这些方法间传播。 |
redis相关的八股文:
知识点 | 解释 |
redis与本地缓存 | 本地缓存:轻、快速但是存在一致性问题、具有生命周期性。redis(分布式缓存):缓存具有一致性,但是集群复杂 |
redis的字典 | redis字典(dict)底层由散列表实现,每个散列表(dictht)由多个散列节点作为组成,每个散列节点(dictEntry)是一个键值对。 redis:https://www.cnblogs.com/hunternet/p/9989771.html 知识点:1.一个是正常使用,一个用于rehash期间使用。 2.当redis计算哈希时,采用的是MurmurHash2哈希算法。 3.哈希表采用链表法解决散列冲突,被分配到同一个地址的键会构成一个单向链表。 4.在rehash对哈希表进行扩展或者收缩过程中,会将所有键值对进行迁移,并且这个迁移是渐进式的迁移。 |
redis结构 | String的使用场景:信息缓存、计数器、分布式锁(int row(大于32字节) embstr) hash的场景:商品信息(zblist,hash表) list常见:定时排行榜 (zblist 双向链表(quicklist),linklist) set的场景:收藏文件夹(inset hash表) soetSet场景:实时排行榜(ziplist,skiplist) Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。 HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。 Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组 |
redis基础结构 | dict(数据库中的键值对由字典保存)、sds、ziplist、skiplist |
redis快的原因 | 1.结构简单 2.内存操作3.单线程4.多路复用 虚拟内存机制:Redis直接自己构建了VM机制 |
redis淘汰策略 | 这八种大体上可以分为4中,lru、lfu、random、ttl。lru:Least Recently Used),最近不使用 lfu:Least Frequently Used,最不经常使用法 ttl:Time To Live,生存时间 random:随机 volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰; |
AOF和RDB | 优先使用AOF,RDB用来备份 AOF是采用追加方式、每次、一秒、系统(30s) RDB:save/bgSave save回阻塞、bgsave会创建fork进程 |
redis过期删除 | 定时过期(针对每个redis数据) 惰性过期(用到才删除)定期过期(定时任务) |
redis事务 | Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的。不支持回滚,采用lua实现原子 |
redis的高可用 | 主从模式,哨兵模式,集群模式。 1. 哨兵模式是在主从模式的基础上添加了故障检测和自动故障转移的功能。 2.集群模式是在多个Redis节点之间分配数据,提供更高的可扩展性和容错能力。在集群模式中,数据被分配到多个Redis节点上,每个节点处理自己的数据。当一个节点失效时,集群会自动将这个节点的数据迁移到其他节点上。 |
redis哨兵模式 | 1.集群监控2.消息通知3.故障转移4.配置中心。master挂了,需要大部分哨兵同意,分布式选举(Raft 算法),主sentila和从sential |
中间件主从复制对比: | redis:完全复制+部分复制 zookeeper:HA+epoch |
中间间一致性hash问题: | redis:采用hash槽的,每个key通过hash对总槽(16384(16k))数取模判定槽的位置,集群每个机器负责一部分hash槽 |
一致性hash: | 解决的问题:为了在节点数目发生改变时尽可能少的迁移数据。例如:在做缓存集群时,为了缓解服务器的压力,会部署多台缓存服务器,把数据资源均匀的分配到每个服务器上,分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。 原理:根据每台服务器的key(ip) 映射到hash环上,请求对象,依次顺时针移动到对象点上,增减只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。 问题:数据倾斜:服务节点少,数据集中到多个。解决方案:多次hash,计算多个虚拟节点 |
分布式锁: | redis:sentx或者是redLock sentx与redLock区别:sentx针对单个,无法处理单点故障。 redLock使用的多点集群,保障半数申请半数以上。有个TTL内申请完成。 zookeeper:节点排序,watcher监控 mysql:乐观锁 |
缓存解决问题: | 雪崩(大面积失效):1.随机过期时间 2.并发量不大,采用锁 3.失效标记,及时更新缓存。 4 后端限流 穿透(缓存、数据库中都没有):1.设置缓存为为null,2.采用布隆过滤器 3.接口层做验证, 击穿(缓存没有,数据库中有):1.永不过期 2.互斥锁 3 软过期+互斥锁 缓存预热:上线时,将数据直接刷新到缓存。1.手动刷新2.定时刷新3.@postContruct 缓存降级:超时、警告(人工降级)、错误率、严重错误率。 热key失效:加锁,对与失效的热key,先加锁、然后查询DB,更新到缓存,解锁 热点问题:多级缓存(本地缓存、代理拓展)、多副本(不一致问题)、迁移单个节点(隔离) 热key发现:历史数据统计、客户端统计、抓包统计、异步集群统计 大key:将大key拆分、压缩 |
缓存与数据库双写不一致 | 缓存不一致问题:(查询、更新数据) 并发小的时候: 对于查询:缓存中没有,先查查询数据库,获取到数据,然后更新缓存,返回给请求方。 更新:先写数据库,然后在操作(删除)缓存(并发查和更新会出现小概率不一样) (无论谁先更新都会有对应的不一致问题,redis频率高,触发的概率更高) 查询:与并发小一样 兜底逻辑:删除缓存要重试、定期全量更新缓存、设置过期时间 |
redis和menCahed区别 | 1.redis有丰富的数据结构,menCached只有String 2.redis与有持久化 3.redis速度要高于menCached(多线程要不断切换) |
redis常见面试问题 | 1.1亿数据获取部分数据:scan指令,可能有重复 2.异步队列,采用list结构,rpush,lpop 3.延时队列:采用sortSet,按照时间排序 |
mysql相关八股文:
知识点 | 解释 |
MySQL常见面试题汇总(建议收藏!!!) | |
MYSQL全表遍历性能 | 带索引链式遍历: select * from a where id > 10000 order by id limit 10000; id分区实现多线程遍历:1.select min(id),max(id) from a limit 100;2.select * from a where id >= 1 and id <= 10000; |
char和varchar | char是固定数组(身份证号、电话号码)、varchar是动态数组 |
三大范式 | 1. 原子性 2.不能有部分依赖 3. 不能有传递依赖 |
索引是啥 | 排好序的快速查找数据结构 |
mysql索引使用判断 | 1.select A,id from test where B = 1 AB为索引、会触发使用AB索引。理解索引节点上存储AB+ID+ref (覆盖索引)覆盖索引对InnoDB尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引包含查询所需的数据,就不再需要在聚集索引中查找了。 2.比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的 3.B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据 |
hash索引和B+索引 | 1. B+多路平衡树 2. hash索引 其实就是map mysql中B+树的叶子节点之间是双向的指针 |
B+树vsB树 | 1. 高度更矮 2. 结点不是所有数据,可以回表 3. 底层节点是双向链表 |
聚集(簇)索引和非聚集索引 | mysql聚集索引和非聚集索引的区别是什么-mysql教程-PHP中文网 聚集索引:就是以主键创建的索引,在叶子节点存储的是表中的数据。 聚集索引中表记录的排列顺序和索引的排列顺序一致。 聚集索引是物理上连续存在 非聚集索引:就是以非主键创建的索引(也叫做二级索引),在叶子节点存储的是主键和索引列。 非聚集索引中表记录的排列顺序和索引的排列顺序不一致。 非聚集索引是逻辑上的连续,物理存储不连续。 聚集索引每张表只能有一个,非聚集索引可以有多个 |
创建索引原则 | 1.最左前缀匹配原则2.频繁查询建立索引3更新频繁的不适用于索引4.区分度高适合建5.拓展索引优先 6.外键要建 |
不使用索引情况 | 使用不等于查询 列参与了数学运算或者函数 在字符串 like 时左边是通配符。类似于’%aaa’。 当 mysql 分析全表扫描比使用索引快的时候不使用索引。 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引。 |
binglog | 记录sql执行日志,有statement、row、fix。row记录上下文信息、statement记录单条执行信息,fix混合使用 |
redolog和binlog | relog:1.是物理日志,记录做了什么修改,2.InnoDB特有 3.固定大小 4.crash-safe能力(redo log 只会记录未刷盘的日志,binlog全部日志,没有标示区分为未刷盘) binglog:1.是逻辑日志,每条sql记录,2.mysql所有、所有引擎可用3.追加写 |
mysql二段提交 | 如果redo log持久化并进行了提交,而binlog未持久化数据库就crash了,则从库从binlog拉取数据会少于主库,造成不一致。因此需要内部事务来保证两种日志的一致性。 prepare:redolog写入log buffer,并fsync持久化到磁盘,在redolog事务中记录2PC的XID,在redolog事务打上prepare标识 |
脏页 | 当内存数据和磁盘数据内容不一致的时候,我们称这个内存页为脏页;内存数据写到磁盘后,内存的数据和磁盘上的内容就一致了,我们称为“干净页”。 |
buffer pool | 1. 数据缓存页(控制块、缓存页),key为表空间号+数据页(page) 2. 管理:free list(管理空闲页)、flush list(管理脏页)、lru list(脏页+clean页) 脏页既存在于LRU列表中,也存在于Flush列表中 LRU mysql改进算法:冷/热数据区,插入时插入到热数据区的尾部, 普通LRU全表扫描会将热数据替换。 3. changge buffer: 只会用到非唯一索引。唯一索引要校验唯一性 |
页 | 页头:上下指针,形成一个页的双向链表 用户区:数据记录,是单向链表 目录区:维护最小记录和最大记录。单页链 |
uuid和自增id | 自增:内存小、速度快,但是 不支持分页、爬虫容易爬取、锁问题 UUID: 支持分页 缺点速度慢 |
explain sql 常见问题总结: | 1.using filesort:filesort是通过相应的排序算法,将取得的数据在内存中进行排序(没有使用到索引)解决方式:1.添加索引2.分页查询ID,left join 查询 参考:Mysql 查询优化之 Using filesort - 知乎 注意:必须依照顺序,在创建组合索引时,where条件的字段在orderBy的字段之前,如果orderBy是多字段,则必须依照顺序创建 详情可参考链接:mysql order by 造成语句 执行计划中Using filesort,Using temporary相关语句的优化解决_清风远行的博客-CSDN博客 2.use tempory:创建临时表(jion操作时候)如果有ORDER BY子句和一个不同的GROUP BY子句,或者如果ORDER BY或GROUP BY中的字段都来自其他的表而非连接顺序中的第一个表的话,就会创建一个临时表了。 |
explain | type数据: extra: |
分页优化 | 1. 利用索引的优化 2. 利用子查询优化 |
sql时间长 | 1. 锁竞争、QPS太高、IO大、CPU耗时 2. 索引性能问题:是否走索引 3. join和分页 |
join优化 | |
MyISAM与Innodb | 1.MyISAM只有表锁Innodb还有行锁、 2.前者不支持事务后置支持 3.前者没有外键和哈希索引 4.mylsam以插入为主,innodb:支持大量更新、并发以及其特殊属性 5、InnoDB 是聚集索引,MyISAM 是非聚集索引 |
select count(*)时InnoDB和MylSAM分别是怎么处理的 | InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快; |
mysql四大特性 | 1.插入缓存(插入效率) 2.二次写((部分页失效)就是在写数据页之前,先把这个数据页写到一块独立的物理文件位置(ibdata),然后再写到数据页。这样在宕机重启时,如果出现数据页损坏,那么在应用redo log之前,需要通过该页的副本来还原该页,然后再进行redo log重做) 3.自适应hash索引 4.预读(预见下一次读的数据(顺序、随机)) |
mysql事物 | mysql的四大特性分别是:原子性,一致性,隔离性和数据持久化 mysql四大隔离级别: 分别是:读未提交(READ UNCOMMITTED )、读提交( READ COMMITTED)、可重复读(REPEATABLE READ)、串行化(SERIALIZABLE) 脏读(Drity Read): 某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。 |
事务是如何通过日志来实现的 | redo log(重做日志) 实现持久化和原子性:一条sql执行时日志先行,提交后就写到磁盘。 undo log(回滚日志) 实现一致性。 redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本 |
purge和drop的区别 | purge是清除表,drop是放入回收站,可以被恢复 |
行锁实现 | innoDB 基于索引来实现的行锁,for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将完成表锁,并发将无从谈起 |
锁 | 1. 使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。 2. 临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免幻读(Phantom Read)。 3. select for update 事物中使用,明确主键是行锁 否则是表锁 |
锁算法 | 1.Record Lock 2.GAP Lock 3.Next-key lock (间隙锁只存在于 RR 隔离级别) |
exist和in | 外层查询表小于子查询表,用exists,外层查询表大于子查询表,则用in,外层和子查询表差不多,两者差不多。in是hash连接,exist是loop。not exists效率会更高(使用索引)。 |
MySQL数据库cpu飙升到500% | top确认是否是mysqlId的问题。使用 show processlist 一般来说会有大量的mysql的session,确认各session发生的原因 |
垂直拆分/水平拆分 | 垂直拆分:如果一个表中某些列常用,另外一些列不常用,水平拆分:表中的数据本身就有独立性 |
mysql恢复 | redolog crash恢复 binglog 系统恢复 |
mysql缓存 | buffer pool:主内存,存储查询的页,LRU算法 内核缓存:flush到磁盘 logbuffer ->内核缓存->磁盘 logBuffer:日志信息 changeBuffer: 记录非聚簇索引(不在buffer pool) 的更改存在时候统一操作 |
主从过程对比: | mysql: 1)、在master机器上的操作: 当主从同步开启的时候,slave上会创建两个线程:I\O线程。该线程连接到master机器,master机器上的binlog dump 线程会将binlog的内容发送给该I\O线程。该I/O线程接收到binlog内容后,再将内容写入到本地的relay log;sql线程。该线程读取到I/O线程写入的ralay log。并且根据relay log。并且根据relay log 的内容对slave数据库做相应的操作。 |
主从延时原因 | mysql:1.主是(顺序写)从是随机写且单线程执行(master可以并发,Slave_SQL_Running线程却不可以) 2.当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围, 解决方案:降低主库压力:主写从读、加入缓存、业务分离、slave比master性能好 |
mysql常见问题 | MySQL常见面试题汇总(建议收藏!!!) |
必须掌握 | 1. sql性能调优 (explain 、慢查询日志、Show Profile分析) 2. 索引优化 3.分库分表(怎么分,分后完整性) 4. 主从复制(延时问题) |
kafka八股文:
知识点 | 解释 |
不错的文章 | 面试题概要解答-消息队列-Kafka 和 RocketMQ - 知乎 二十四个RabbitMQ消息中间件面试题及答案(双手奉上,拿走不行) - 简书 绝对详细的 RabbitMQ入门,看完本系列就够了(一) - 知乎 https://baijiahao.baidu.com/s?id=1765969162341334710&wfr=spider&for=pc |
BBQX | 基于RMQ+ByteDoc实现,在支持BBQ原有能力的同时,提升了稳定性和消息干预能力。目标是代替原本的BBQ成为机审平台消息调度的主链路,并与BBQ互为灾备,支持灵活切换。 一些好的设计: BBQX采用令牌桶算法,redis作为一个桶,每秒拉取一次,下一秒补齐对应的令牌。从pending队列数据中消费对应的数量的case。对于高的可以设置多个桶 内容池: 主要是记录单条case的状态信息。公司RMQ没法办支持过高qps的查询。 |
mq主要在哪些场景中使用: | 1、高峰流量:抢红包、秒杀活动、抢火车票等这些业务场景都是短时间内需要处理大量请求,如果直接连接系统处理业务,会耗费大量资源,有可能造成系统瘫痪。 而使用MQ后,可以先让用户将请求发送到MQ中,MQ会先保存请求消息,不会占用系统资源,且MQ会进行消息排序,先请求的秒杀成功,后请求的秒杀失败。 2. 消息分发:如电商网站要推送促销信息,该业务耗费时间较多,但对时效性要求不高,可以使用MQ做消息分发。 3. 数据同步:假如我们需要将数据保存到数据库之外,还需要一段时间将数据同步到缓存(如Redis)、搜索引擎(如Elasticsearch)中。此时可以将数据库的数据作为消息发送到MQ中,并同步到缓存、 搜索引擎中。 4. 异步处理:在电商系统中,订单完成后,需要及时的通知子系统(进销存系统发货,用户服务积分,发送短信)进行下一步操作。为了保证订单系统的高性能,应该直接返回订单结果,之后让MQ通知子系统做其他非实时的业务操作。这样能保证核心业务的高效及时 5. 离线处理:在银行系统中,如果要查询近十年的历史账单,这是非常耗时的操作。如果发送同步请求,则会花费大量时间等待响应。此时使用MQ发送异步请求,等到查询出结果后获取结果即可。 |
kafka vs rocket mq | 1. 事务不一样 2. rocket mq 中 queue 对应partition 3. kafka 的主从是逻辑概念,用zk管理, rocket mq 是物理概念,用自己的server管理。 性能:Kafka 单机写入 TPS 号称在百万条/秒。RocketMQ 大约在 10万条/秒。 分布式事务消息:Kafka 不支持分布式事务消息。RocketMQ 支持分布式事务消息。 延时消息:Kafka 不支持延时消息。RocketMQ 支持延时消息。 架构:RocketMQ 没有使用 Zookeeper,使用 NameServer。Master/Slave 概念差异。 |
kafka事务 | 采用2PC(两段式协议) 参考阿里面试官:RocketMQ与Kafka中如何实现事务?_Java架构技术官的博客-CSDN博客 准备阶段,生产者发消息给协调者开启事务,然后消息发送到每个分区上。 提交阶段,生产者发消息给协调者提交事务,协调者给每个分区发一条“事务结束”的消息,完成分布式事务提交。 |
rocketMq事务 | 采用2PC(两段式协议)+补偿机制(事务回查)的分布式事务功能RocketMq之事务消息实现原理-腾讯云开发者社区-腾讯云 1.发送方发送half消息、mq确认、发送方执行本地事务、二次确认,消息投递 发送流程:发送half message(半消息),执行本地事务,发送本地事务执行结果,消息commit 定时任务回查流程:MQ定时任务扫描半消息,回查本地事务,发送事务执行结果 |
事务对比 | RocketMQ的事务适用于解决本地事务和发消息的数据一致性问题。 Kafka 的事务则是用于实现它的 Exactly Once 机制,应用于实时计算的场景中。 RocketMQ 是把这些消息暂存在一个特殊的队列中,待事务提交后再移动到业务队列中; Kafka 直接把消息放到对应的业务分区中,配合客户端过滤来暂时屏蔽进行中的事务消息; |
kafka消费组 | 一种具有拓展性和容错性的消费机制。多个实体组成的一个小组,消费若干个主题。 位移提交的过程:Consumer 需要上报自己的位移数据,这个汇报过程被称为位移提交。因为 Consumer 能够同时消费多个分区的数据,所以位移的提交实际上是在分区粒度上进行的,即Consumer 需要为分配给它的每个分区提交各自的位移数据。 面试点:消费者位移提交(自动提交(提交位移的Deadline时间点)、手动提交、consumer_offset(groupId + topic + partion+offset)) reblance(再平衡):range(基于单个主题)、RoundRobin(基于全部主题)、排序重平衡 消费组与broker:一个partion只会有一个consumer消费 |
Reblance场景 | 1.订阅主题数量发生变化 2.组成员数量发生变化3.订阅主题分区数发生变化 |
mafka Reblance消费优化 | 避免Rebalance 造成消息重复消费,引入了Gap 机制,30s_gap。 消费者和castle之间通过消费者主动定期发起心跳请求来获取最新的相关配置,其中包括partition分配的情况,在一个消费者c1两次心跳间隔中,有另外一个消费者c2上线,如果此时进行重新分配,那么c2会立即从c1分到一个partition,c2开始消费,但是c1要等到下一个心跳请求时才能感知p2这个partition被分配走,所以在这段时间内,p2这个partition是同时被两个消费者消费的,这就造成了消息重复消费。 这种分配机制是在有消费者上线的情况下,如果需要从其他消费者手中获取partition,变动的这一部分partition先不下发给新上线的消费者,而是暂时挂起,即谁都不给分,然后等待30s后,再把这些partition分给新上线的消费者。 避免了Rebalance 的重复消费但是就可能会造成消息的积压,不建议在业务消费MQ 高峰进行服务的上线发布 |
消费效率 | 1.优化消费逻辑——降低消费逻辑处理耗时 2.增加单机消费线程数——提升单机消费能力 3.调整消费模式——让消费者负载更均衡 4.调整消费策略——减少跨地域调用的网络时延 5.延时消息 |
kafka中zookeeper作用 | 目前是(KIP-500/Raft 的共识算法):元数据存储(主题、分区信息)、节点管理、选举 |
位移 | kafka为每个消息分配的位移ID,用来区分其在分区中的位置。一旦被写入就不可以被修改 位移值和消费者位移:位移(offset),消费者位移(groupId + topic + partion+offset) log文件结构:**.index + **.log |
leader与follower | kafka副本分为leader副本和follower副本,leader副本用来对外提供服务的、follower副本pull方式获取数据。 leader副本与follower副本一致性:高水位(leader变更会出现问题)、Leader Epoch机制(版本+位移) |
heap size | 1.默认JVM执行,稳定时,执行一次FULL GC,查看GC后遗留下对象的大小M,设置堆的大小为1.5M到2M左右,kafka默认时6G |
leader为存在问题 | 删除 ZooKeeper 节点 /controller,触发 Controller 重选举。 Controller 重选举能够为所有主题分区重刷分区状态,可以有效解决因不一致导致的 Leader 不可用问题。 |
kafka高性能原因 | 1.顺序写磁盘 2.os cache 3.零拷贝 (先cache然后顺序磁盘) |
zero copy | 采用DMA技术(一种硬件直接访问系统主内存的技术)。传统要经过cpu多次从用户态切换到核心态 |
follower消息同步 | fetch消息请求、读取log,更新内存中Follower leo值,在更新fetch 中leo值,在更新分区高水位值,follower获取到fetch时,写入日志,更新LEO和HV。错配问题 |
mafka延时队列 | 1.Producer发送消息到DelayServer上,携带延迟的相对值,即从现在开始,多久后消费;2.DelaySever收到消息后,将延迟相对值加上当前时间,即到期时间,作为主key,消息体作为Value,发送到Tair;3.DelayServer节点通过心跳获取到castle下发的主题,各个节点只处理分发给自己主题,根据时间戳和主题名称组成key,从Cellar中取到期消息;4.取出消息后发送给Mafka Broker,后续流程与非延迟消费消息处理逻辑一致,mafka客户端消费消息; |
延时队列 | 1.jdk DelayQueue 2.Quartz 定时任务3.Redis sorted set4.Redis 过期回调5.死信交换机6.时间轮 |
中间件对比: mafka对比kafka、rocketMq、rabbitMQ gravity对比sential | mafka改进:消费回溯/前进、消息延迟(消息粒度延迟,消费组延迟)、消息轨迹、消费者黑名单、配置统一管理、并行消费、死信队列 |
延时队列 | rabbitmq本身是不直接支持延时队列的,RabbitMQ的延迟队列基于消息的存活时间TTL(Time To Live)和死信交换机DLE(Dead Letter Exchanges)实现:
|
mq选型 | activeMq、rabbitmq 吞吐量小,rabbitmq采用erlang编写。kafka、mafka、rocketmq支持大数据场景 |
mq保障消息不丢失 | 1.生产者丢失(网络抖动、消息体量大):采用callBack方式,重发信息、或者重新调整消息大小 2.消息传输丢失:高水位同步,存在可能丢失。epoll克服这个问题 acks = all。acks 的默认值即为1,代表消息被leader副本接收之后就算被成功发送。 acks = all 代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。 replication.factor >= 3:这样就可以保证每个 分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。 min.insync.replicas > 1:一般情况下我们还需要设置 min.insync.replicas> 1 ,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。 unclean.leader.election.enable = false:当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader 3消费者丢失:先位移在消费就会最多消费一次,先消费后位移,至少消费一次 |
如何保证消息不丢失:消息丢失了? | RMQ:
kafka:
解决方案:
|
网络八股文:
知识点 | 解释 |
http和rpc | Http和Rpc是两个维度的东西 1. HTTP 是一个协议,是一个超文本传输协议。不提供数据包的传输功能,数据包从浏览器到服务端再来回的传输和它没关系。客户端和服务端约定好的一种通信格式。 2. RPC 则是远程调用,其对应的是本地调用。RPC 的通信可以用 HTTP 协议,也可以自定义协议,是不做约束的。 rpc相比于http简单方便,http通用,第三方协议一般用http |
http、rpc、tcp、ip、socket间关系 | 1.TCP/IP协议是传输层协议,主要解决数据如何在网络中传输。“IP”代表网际协议,TCP和UDP使用该协议从一个网络传送数据包到另一个网络。socket个基于TCP/IP协议的编程接口,门面模式。 2.HTTP是应用层协议,主要解决如何包装数据。 4.RPC(Remote Procedure Call,远程过程调用)是建立在Socket之上的,一台机器上运行的主程序,可以调用另一台机器上准备好的子程序。 (我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP 文本信息,然后使用TCP/IP做传输层协议将它发到网络上。) |
http有状态无状态问题 | 1.无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息 2.无状态协议解决方法:通过1、cookie 2、通过session会话保存 |
https的一次过程 | 1.解析URL,判断此URL是否为合法的链接 2.解析域名(DNS):从浏览器自身的缓存中解析此域名数据、从本地电脑的HOST文件中解析域名或者通过DNS服务器(移动、电信、联通)解析域名。 3.拿信息:IP和端口信息 4.封包并进行三次握手:浏览器将请求信息进行打包,通过TCP的三次握手将数据传递至服务器。 https:SSL握手: 4.1证书验证:客户端向服务器发送 HTTPS 请求,等待服务器确认。服务器将 crt 公钥以证书的形式发送到客户端,该证书还包含了用于认证目的的服务器标识,服务器同时还提供了一个用作产生密钥的随机数。服务器端存放 crt 私钥和 crt 公钥。 4.2获取对称密钥:客户端用随机数和 hash 签名生成一串对称密钥(即随机钥,客户钥),然后用 crt 公钥对对称密钥进行加密。客户端将加密后的对称密钥发送给服务器。服务器用 crt 私钥解密,取出对称密钥。 4.3服务器用随机钥来加密数据,发送加密的网页内容。 5.服务器解析、处理、返回数据 6.浏览器从CDN服务器拿到数据、通过加载资源、渲染页面等操作,将页面展现给用户。 |
http三次握手与四次挥手 | 三次握手做了啥:客户端和服务端都能确认对方有发送能和接受能力 挥手为啥多了一次:服务器在发送是否可以断开链接,得到回复后,但是未必数据传送完成。所以得发送完成后在发送确认消息。 |
http数据传输(异常容错) | 1.分片 2.预热(ip寻址) 3.减半 4 管线技术(持久连接上,批量发送) 5 压缩编码 |
https常见错误码及排查方式 | 301、302:URL重定向。访问的指定URL被重定向到另外URL。通常是网站代码自身逻辑,rewrite规则也可设置。 403:权限问题 404:没找到资源。所请求的资源路径不存在。通常是由于网站根目录中没有对应的资源导致,特殊情况有可能由于未部署运行环境缺运行代码导致。 502:网关错误,序网关响应慢 409:当客户端试图执行一个”会导致一个或多个资源处于不一致状态“的操作时,发送此响应代码 |
多线程请求,如何让区分 |
锁相关技术:
知识点 | 解释 |
synchronized 锁升级原理 | 在锁对象的对象头里面有一个 threadid 字段,第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。 |
锁级别 | 1. 偏向锁:大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。建而言之,你被我标记了,下次还可以进入 2. 自旋锁 自旋的去等待一会然后在去请求锁。状态的切换消耗资源 3. 轻量锁 通过cas获取锁,如果没有获取到,然后自旋在去请求下锁 4. 重量锁 当获取不到对应的锁,直接退出 |
lock与synchronized区别 | Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。 |
CAS缺点 | ABA 问题、循环时间长开销大、只能保证一个共享变量的原子操作 |
综合会问的问题:
问题 | 解释 |
Kafka/RMQ | 1. 事物问题 2. 选型问题 |
redis常见面试问题 | 1. 缓存问题,保障一致性的问题 2. 各个实际情况场景如何使用 3. 分布式锁的问题 4. redis分布问题 |
mysql | 1. sql性能调优 (explain 、慢查询日志、Show Profile分析) 2. 索引优化 3.分库分表(怎么分,分后完整性) 4. 主从复制(延时问题) |
奇奇怪怪的技术:
问题 | 解释 |
字典树对比hashmap | 1.字典树相同前缀的在同一颗树上,比hashmap省空间。 2.查找或者插入,与树的高度有关,即字符串的长度。其查找和插入的时间复杂度为o(k) |
红黑树 | 1.包含红黑节点、红节点子节点是黑节点、root和叶子节点是黑节点、任意节点到叶子节点黑节点数量相同 |
事务的状态 | 1.活动状态 2.部分提交状态3.提交状态4.中止状态5.失败状态 |
六大原则 | 开单依接知里(开闭、单一职责、依赖倒置、接口分离、最少知道、里氏) |
幂等的方式 | 其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。实现幂等很简单:①先查询一下订单是否已经支付过,②如果已经支付过,则返回支付成功;如果没有支付,进行支付流程,修改订单状态为‘已支付’。方式有:乐观锁(ABA)、(防重表(锁功能)、分布式锁、token令牌(两阶段))、支付缓冲区、唯一约束 乐观锁 > 唯一约束 > 悲观锁 |
幂等VS防重 | 重复提交是在第一次请求已经成功的情况下,人为的进行多次操作,导致不满足幂等要求的服务多次改变状态。幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化。 |
超卖/秒杀 | 1.分布式锁,保障只有一个线程操作 2.将商品信息放到一个队列中去:引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。缺点:每个商品都要准备队列 3.乐观锁 4.两段式更新,将数据存储在redis中,Redis的原子自增操作,异步更新到DB中 |
技术亮点 | 亮点一定是你在公司岗位上提供的一些好的解决方案。也许你在项目中通过一些很优雅的方式完成了某个复杂逻辑的设计,或者你为了解决一些重复劳动开发了一套自动化解决方案提升了工作效率,或者你开发了一个技术产品能够服务整个公司解决一些技术难题,或者再精细化一点,在流量特别大的时候你优化了一个接口提升了访问效率。 案件库的状态流转:松鼠状态机+观察者模式+定时补偿。解耦了状态变更+回放、重审、消息、数据库更新,redis使用 |
堆与栈 | 1.堆是不连续,栈是连续 |
常见hash算法 | Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法 |
知识点 | 详细解析 |
---|---|
hash冲突解决方案(扩容) | 1.redis:采用了 1.Java:采用链表+红黑树解决,扩容是在原本基础上扩容,Java8 rehase是加上原本的基数 3.list:ArrayList是采取延迟分配对象数组大小空间的,当第一次添加元素时才会分配10个对象空间,当添加第11个元素的时候,会扩容1.5倍,当添加到16个元素的时候扩容为15*1.5=22,以此类推。ArrayList每次扩容都是通过Arrays.copyof(elementData, newCapacity)来实现的。 4.string: 5.sds:扩容时预分配一些空间 2.字符串缩短时候,惰性收回空间 6.threadLocalMap:扩容因子是,长度的2/3.当当大于扩容因子,先进行rehash,在rehash会进行探测式清洗,剩余数据大于3/4的扩容因子,就进行扩容 |
zookeeper节点 | 1.持久目录节点2.临时目录节点(绑定客户端会话)3.持久顺序编号节点4.临时顺序编号节点 持久:不会被删除 临时:会话结束也删除 顺序编号节点:多个相同会创建多个 4种节点 |
watcher | zNode上注册watcher,指定触发事件,触发对应的watcher,watcher不是永久的,一次性的触发器 |
zookeeper事务的顺序 | zookeeper采用全局的事务递增的id来标识,所有的倡议都会采用zxid |
集群、分布式、微服务 | 集群:同一个业务,部署在多个服务上。分布式:同一个业务,分成多个子业务,部署到不同的服务上。微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。 |
CAP与BASE理论 | CAP(一致性、分区容错、高可用) BASE理论(基本可用、软状态、最终一致性) |
线程安全的类(8个) | Vector HashTable StringBuffer ConcurrentHashMap stack enumeration Atomic类 blockQuere copyAndWritrArray |
ZooKeeper数据模型 | 分布式层次的基本结构、类似于文件系统,zookeeper 的watcher节点。使用场景:观察者消费者、master选举、分布式锁 |
CMS和G1 | G1:对内存进行分割,划分为一个区域(region),多个region组成,老年代、新生代、幸存区相比与CMS,G1的时间可以预期。相对于CMS的优势而言是内存碎片的产生率大大降低。 CMS:初始标记-->并发标记--->在标记-->回收。浮动垃圾+内存碎片 回收器历程:串行收集-->并行收集---->CMS--->G1 G1相比较CMS的改进: 算法: G1基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次FULL GC。 停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。 并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。 CMS中,堆被分为PermGen,YoungGen,OldGen;而YoungGen又分了两个survivo区域。在G1中,堆被平均分成几个区域(region),在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。 |
拥塞控制 | 1.慢启动 2.拥塞避免 3.快速重传 4.快速恢复 |
用户态和内核态 | 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。 原因:由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态和内核态。 切换方式:系统调用、异常、外围设备中断 |
UDP与TCP | UDP:无链接、支持一对多、多对多、多对一的连接、面向报文、不可靠的、头部开销小、传输高效 TCP:面向链接、仅仅支持一对一、面向字节流、可靠(可以判断丢包)、拥塞控制、全双工(tcp两边设有缓存) |
常见协议 | zookeeper:zap协议(选举、恢复(发现、同步)、广播(每个请求一个事物ID,半数同意算成功)) redis:gossip协议:类似于瘟疫传播:https://www.cnblogs.com/charlieroro/articles/12655967.html |
在结束连接的过程中,为什么在收到服务器端的连接释放报文段之后,客户端还要继续等待2MSL之后才真正关闭TCP连接呢? | 1.需要保证服务器端收到了客户端的最后一条确认报文 1.防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失) |
函数a调用函数b的过程,是怎么传参的 | 在Java中,我们声明并初始化一个变量的时候,会产生一个基本类型或者对象的引用以及其这个基本类型或者对象本身,而我们代码中看到的变量就是这个引用,无论何时我们想调用这个基本类型或者对象的时候我们只能通过其引用来间接调用。 Java函数传递时,基本类型传递的是值(把实参的值拷贝一份传给形参,之后在函数体中所有的操作都是在形参上,永远不会改变实参的值),对象类型传递的是引用,无论是基本类型还是对象类型,在函数体中没有改变对象的操作的话原来对象就不会改变! |
管道与队列 | 管道:是以文件位介质的,进程间相互通信的,linux系统下的 队列:以缓冲区位介质的 进程间通信方式:信号、信号量、管道、队列、socket、共享存储 |
索引对比 | 1.mysql B+树 ,辅助索引+聚簇索引 2.kafka:Kafka 中的索引文件以稀疏索引(sparse index)的方式构造消息的索引,它并不保证每个消息在索引文件中都有对应的索引项。 1.先找日志分段、读取索引文件、找到不大于offer-baseSet的最大索引、读取日志、获取数据 场景决定它们的存在可能:Kafka中的这种方式是在磁盘空间、内存空间、查找时间等多方面之间的一个折中。mysql索引性能高于kafka ,但维护困难 |
BIO、NIO、AIO | NIO 多路复用(epoll比(select、poll)上限高,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。)、AIO 采用的是订阅-通知方式 |
符号引用、直接引用 | 1.符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。 2.直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。(直接指针、相对地址、句柄) |
事务失效的情况 | 1.底层数据库不支持事物 2.在非public修饰的方法使用,@Transactional注解使用的是AOP,在使用动态代理的时候只能针对 3.事物内部采用try---catch吃掉异常 4.同一个类中,A方法中调用B,B使用事物注解(Spring在扫描Bean的时候会自动为标注了 5.rollbackFor设置错误,导致某些异常不回滚 数据库、public、异常被吃、内部调用、rollback设置错误。 |
JVM如何理解泛型概念 | ① 虚拟机中没有泛型,只有普通类和方法。 |
1.8/1.7JVM 内存模型区别 | 元数据区取代了永久代,元数据空间并不在虚拟机中,而是使用本地内存。 |
1.8新特性 | 1.函数表达式2.lamada表达式3.stream API4.方法引用和构造器引用 |
stream和for谁快 | for更快,stream是按照每行读取,不说一次性读取数据 |
集合存储null | list多个、set单个、sortSet不能(底层是红黑树),hashTable和currentHashmap不能(多线程时,为null不能判断是为null还是并发问题) |
间隙锁 | 意向锁是一种不与行级锁冲突表级锁。 插入意向锁,本质原理也是一个间隙锁 索引插入意向锁,是为了解决同一个间隙内,并发插入不同位置的锁竞争。 |
ArrayList、LinkedList、Vector的区别 | 1.ArrayList:数组 Vector:数组 LinkedList:双向链表 2.Vector:线程安全 ArrayList:非线程安全 LinkedList:非线程安全 3.Vector:缺省的情况下,增长为原数组长度的一倍。说到缺省,说明他其实是可以自主设置初始化大小的。 ArrayList:自动增长原数组的50%。 |
并发juc | LinkedBlockingQueue : ArrayListBlockingQueue 1、可以理解为容量为1的阻塞队列,只有被消费了,才能接受生产者的消息。 copyonwritelist 一个写时复制的策略保证 list 的一致性,所以在其增删改的操作中都使用了独占锁 ReentrantLock 来保证某个时间只有一个线程能对 list 数组进行修改。其底层是对数组的修改,调用 Arrays.copyarray() 方法进行对数组的复制,在底层还是调用的 C++ 去进行的数组的复制 System.copyarray() |
redis | list:在Redis中,有一种压缩列表的存在zipList,它把少量的元素使用一个连续的内存空间,就像时数组一样,可以节省内存,而list结构就是由多个这种zipList串起来组成的,被称为快速链表quickList。 skiplist:Node节点,有向下,向右的指针 |
B+节点大小 | 一个页的大小,或者倍数,树高三层 |
Java 价格 | BigDecimal |
bit转化为String | String str = new String(byteArray); |
指令重排 | 一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。 |
场景题
题目名 | 方案 |
redis阻塞 | 1.大对象,将大对象拆成小对象 2.并发量太高,cpu使用高 3.执行算法密度高的命令,如用get,用scan代替 4.持久化.(fork执行、AOF) 外在:网络、cpu竞争、内存紧张 |
es脑裂 | 1.网络原因、认为主节点挂掉 2.master节点负载过大、心跳异常 3.GC问题,STW 核心:认为主节点挂了 |
谈线程安全 | 1.竞争临界资源 2.java内存模型,主内存、工作内存 3.解决方案,加锁 、队列、CAS |
如果redis也扛不住(秒杀) | 1.做题 2 消息队列 |
kafka消息丢失 | 1.Producer 生产数据默认是先写到内存(PageCache)中的,定期 flush 到磁盘上。flush不保障消息是否丢失 2.offset 的自动提交。消费者会自动每隔一段时间将offset保存到一个分区上,此时如果刚好将偏移量提交到分区上后,但这条数据还没消费完,机器发生宕机,此时数据就丢失了。 在平衡、机器宕机! Q1 如果提交的偏移量小于客户端处理的最后一个消息的offset,则两者之间的数据就会被重复消费。 |
kafka重复消费 | 1.在消费者自动提交offset到zookeeper后,程序又消费了几条数据,但是还没有到下次自动提交offset到zookeeper之时,如果机器宕机了,然后重启,此时消费者会去读zookeeper上的偏移量进行消费,这就会导致数据重复消费。 解决方法:关闭自动提交,改成手动提交。 2. |
mysql增加一个属性 | (1)alter table add column?不太可行,锁表时间长 (2)新表+触发器?如果数据量太大,新表不一定装得下,何况触发器对数据库性能的影响比较高 解决方案:“version+ext”或者“key+value” |
redis管道 | 可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline 通过减少客户端与 redis 的通信次数来实现降低往返延时时间,而且 Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。 |
Java反射 | (加载(获取这个文件)、验证、准备、解释、初始化) Java的反射就是利用上面加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。 class获取方式:.class .getClass Class.forName |
hashmap、linkhamap、treemap | hashmap:顺序随机 linkhashmap:保证插入的顺序 treemap:继承sortMap ,根据键值进行排队 |
hashmap hashtable currentHashmap | hashmap:线程不安全、初始化为16、链表+数组+红黑树、key能为null hashtable:线程安全、并发时候锁住整个table(采用sycnize)、初始化为11、Dictionary 类 currentHashmap:线程安全、syhcnize+cas |
线程池的面试题: | 1.线程池被创建后如果没有任务过来,里面是不会有线程的。预热:全部启动或者启动一个 2.核心线程数不会回收 3.核心线程数改变:线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。 对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收; 对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务 |
阻塞队列 | 其实阻塞队列实现阻塞同步的方式很简单,使用的就是是lock锁的多条件(condition)阻塞控制 |
自曾id用完了 | 这问题没遇到过,因为自增主键我们用int类型,一般达不到最大值,我们就分库分表了,所以不曾遇见过 升级为:bigInt 表小用:alter,量大采用主从,先更新从在更新主 |
mysql的ACID | 1.原子性:undo log 2.持久性:redo log |
MySQL隐式类型转换 | 当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。 demo:某两列 name='11' , name = '11aa' 。 where name = 11 , 可以查到 '11' 和 '11aa' 两个结果, demo:age=2的数据有2条。where age = 2 , 可以正常查到数据 ; 而 where age = '2aabbcc',查到的数据结果和 where age = 2 是一样的 MySQL 字符串类型用数字可以查出来 MySQL字符串类型会转换成数字 MySQL隐式类型转换_mysql字符串类型用数字类型查询多出数据‘_HaHa_Sir的博客-CSDN博客 |
分布式脑裂解决思路 | 1.仲裁:当节点出现分歧时,由第三方仲裁者决定谁是Master。 2.隔离:当不能确定节点状态时,直接将节点隔离,确保不影响集群运行。 3.冗余:节点间多增加“心跳线”,尽量减少网络隔离带来的影响。 软件层面这里我们通过引入租约机制来解决脑裂问题:Master节点申请租约,在租约时间范围内承认其Master的角色,当租约过期后需要续约,维持自己Master的角色;如果租约过期后没有续约,那么就退出Master角色,重新选主。 |
线程池使用参数设置 | 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换。 并发不高、任务执行时间长的业务要区分开: IO密集型的任务,因为IO操作并不占用CPU,可以加大线程池中的线程数目,让CPU处理更多的业务 CPU密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换。 并发高、业务执行时间长,在于整体架构的设计,能否使用中间件对任务进行拆分和解耦。 |
核心线程数大小 | 1.CPU密集型 :CPU核数 + 1 2.IO密集型:IO密集型任务线程并不是一直在执行任务,则应该配置尽可能多的线程 CPU核数 * 2 CPU核数 / (1 - 阻塞系数),阻塞系数在0.8~0.9之间 两种方式并没有绝对的最优,实际生产环境应该结合压力测试进行调整 |
mq与异步 | |
mybatis一条sql执行的过程 | 1.根据xml或者配置信息,读取sql,生成MapperStatement对象 。 2.接受对应的sql和ID 3.执行器Executor处理接收到接口层传递的SQL的ID和传入参数,根据ID查找对应的MapperStatement,解析MapperStatement对象,得到需要执行的SQL语句并注入传入的参数。获取到数据库连接,将最终的SQL语句到数据库执行,并得到结果。根据MapperStatement对象中的结果映射配置对得到的结果进行转换处理,得到最终的结果。最后释放资源并返回结果到上层。 |
explain 中extra | 1.Using where:SQL使用了where条件过滤数据 2.Using index:SQL所需要返回的所有列数据均在一棵索引树上,而无需访问实际的行记录。 3.Using index condition:确实命中了索引,但不是所有的列数据都在索引树上,还需要访问实际的行记录。 4.Using filesort:得到所需结果集,需要对所有记录进行文件排序。这类SQL语句性能极差,需要进行优化。 5.Using temporary:需要建立临时表(temporary table)来暂存中间结果。 这类SQL语句性能较低,往往也需要进行优化。 group by和order by同时存在,且作用于不同的字段时,就会建立临时表,以便计算出最终的结果集。 6.Using join buffer:需要进行嵌套循环计算。 |
不同对象下的拷贝 | 1.深拷贝、浅拷贝 2.clone 浅拷贝(object)、序列化深拷贝、 3.复制对象:Commons-BeanUtils复制对象、cglib复制对象(浅拷贝) todo:不同类下面的对象拷贝是怎么样的? |
tcp粘包与拆包解决方案 | 1.消息数据的定长,比如定长100字节,不足补空格,接收方收到后解析100字节数据即为完整数据。但这样的做的缺点是浪费了部分存储空间和带宽。 2.消息数据使用特定分割符区分界限,比如使用换号符号做分割。 3.把消息数据分成消息头和消息体,消息头带消息的长度,接收方收到后根据消息头中的长度解析数据。 |
分布式事务解决方案 | 1.定期校对模型。两条链路,一条是主链路、一条是校对链路。(对业务最终一致性时间敏感度较低的业务;跨企业的业务活动;合非可靠消息或消息存在丢失的业务活动;) 2.可靠消息模型(本地消息、中间件消息) 3.TCC模型(2pc,3pc)(2pc:1.请求表决,2.执行提交 3pc:CanCommit阶段、PreCommit阶段、DoCommit阶段 ;TCC:try,confirm,cancel) TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。 4.补偿型 |
代码中遇到进程阻塞,进程僵死,内存泄漏等情况怎么排查 | 通过ps查询状态,分析dump文件等方式排查。 |
Redis内存数据库、共享内存、磁盘数据 | 三者是不同 |
spring、springMVC、springBoot | Spring包含了SpringMVC,而SpringBoot又包含了Spring或者说是在Spring的基础上做得一个扩展
|
海量数据解题思路 | 教你如何迅速秒杀掉:99%的海量数据处理面试题_v_JULY_v的博客-CSDN博客 |
spring处理线程安全问题 | Spring使用ThreadLocal解决线程安全问题 |
指令重排序 | 双重锁模式单例,https://www.136.la/shida/show-366737.html |
内部类与外部类 | 外部类与非静态内部类的”相互可见性“。private这种修饰符并不能阻止外部类直接访问到内部类中的private属性;反之亦然。 |
全局捕获异常 | controllerAdvice + exceptionHandle |
es丢失数据 |
设计题
题目名 | 思路想法 |
限流系统设计 | 短小强悍!一个基于 Redis 的限流系统的设计~-腾讯云开发者社区-腾讯云 可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。 存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。 如何实现?为了控制访问次数,肯定需要一个计数器,而且这个计数器只能保存在第三方服务,比如redis。 大概思路:每次有相关操作的时候,就向redis服务器发送一个incr命令,比如需要限制某个用户访问/index接口的次数,只需要拼接用户id和接口名生成redis的key,每次该用户访问此接口时,只需要对这个key执行incr命令,在这个key带上过期时间,就可以实现指定时间的访问频率。 |
秒杀系统设计 | 秒杀系统设计: 高性能:秒级别的高读、高写 一致性:商品库存高并发时候的准确性能 高可用:大型分布式系统在实际运行过程中面对的工况是非常复杂的,业务流量的突增、依赖服务的不稳定、应用自身的瓶颈、物理资源的损坏等方方面面都会对系统的运行带来大大小小的的冲击。 高性能(读高性能): 1.动静分离 动静分离对于性能的提升,抽象起来只有两点,一是数据要尽量少,以便减少没必要的请求,二是路径要尽量短,以便提高单次请求的效率。具体方法其实就是基于这个大方向进行的。 2.热点优化 1.热点识别(静态热点、动态热点(异步采集、聚合分析)) 2.热点隔离(业务隔离、系统隔离、热点隔离) 3.热点优化 (缓存、限流) 数据的热点优化与动静分离是不一样的,热点优化是基于二八原则对数据进行了纵向拆分,以便进行针对性地处理。热点识别和隔离不仅对“秒杀”这个场景有意义,对其他的高性能分布式系统也非常有参考价值。 3 系统优化 减少序列化、输入输出(IO)、减少日志、框架去除 一致性: 1.减库存:下单减库存、付款减库存、预扣库存 存在问题:恶意下单:反作弊手段识别 超卖:锁、事务 2.高并发读:缓存,分层校验 3.高并发写:redis更新、数据库异步更新。采用更新日志表 高可用(稳定性): 1.答题,登陆验证。防止作弊、延缓请求 2.队列. 请求排队 3.过滤 读限流、读缓存、写限流、写验证 系统可以通过入口层的答题、业务层的排队、数据层的过滤达到流量削峰的目的,本质是在寻求商业诉求与架构性能之间的平衡。另外,新的削峰手段也层出不穷,以业务切入居多,比如零点大促时同步发放优惠券或发起抽奖活动,将一部分流量分散到其他系统,这样也能起到削峰的作用。 Plan B 兜底方案 |
设计消息队列 | 1.生产+服务+消费 服务: 消息的传输:JMS、AMQP(RabbitMQ)(链接协议:AMQP不从API层进行限定,而是直接定义网络交换的数据格式。)、Kafka(一套自行设计的基于TCP层的协议) 消息的存储:内存、本地文件、分布式文件、DB、流数据库 与消费者关系:单播和广播 考虑点:顺序、可靠性、持久化、消息类型、事务 |
文章的评论系统,考察我怎么思考问题,怎么拆分问题 | |
微信朋友圈设计 | 功能:我的朋友圈、好友朋友圈、陌生人朋友圈、黑名单朋友圈 技术点(消息流):push消息(即时消息)、pull消息(服务端收到消息,缓存) 权限控制:读取多个权限规则,取其交集权限,或多个权限规则,若无交集,则取优先权限。 |
IM设计 | 如何设计一个亿级消息量的IM系统_Java_Chank_InfoQ写作社区 |
线上有一个告警,你的解决思路,过程 | 1.了解问题概况,评估影响的范围 针对不同的问题范围直接会影响处理问题的优先级。 服务器监控,服务监控,业务监控和日志系统查看对应信息。 日志系统的trace入手定位问题。 2.明确故障的影响范围 3.深入分析:一个复杂的收集信息-假设-验证—收集信息-假设-验证的循环过程 4.故障重现、排除故障 5.验证问题是否解除 |
解决并发的思路 | 1. 要控制数据,还是控制行为 ? 2、应该选择直接控制还是间接控制 ? 3、尽量使用其他技术来代替锁,不要直接使用锁 ! 4、尽量保持线程间关系的简单清晰,不要设计复杂 ! |
说说你在工作中遇到的最有挑战的问题 | 迁移topic |
统计一亿日活 | 采用位图:位图数据,其实它不是一种数据结构。实际上它就是一个一个字符串结构,只不过value是一个二进制数据,每一位只能是0或者1。redis单独对bitmap提供了一套命令。可以对任意一位进行设置和读取。 一亿数据占据:100000000/8/1024/1024=11.92M 消耗一定空间 HyperLogLog数据结构:,这是一个概率数据结构,用来估算数据的基数。能通过牺牲准确率来减少内存空间的消耗。 |
redis中set于setnx | set 存在即覆盖、setnx时存在不操作,有返回结果 |
redis 过期时间,如何续期 | redissession:看门狗 redis分布式锁的坑:https://www.cnblogs.com/youngdeng/p/12883790.html |
redis自增序列号有什么风险 | 1.redis超时时,不知道时成功还是失败 2.并发量高的话,压力大 3.redis有上线 |
linux排除 | 1.cpu 100% 找到最耗CPU的进程,采用top指令,top -Hp 10765 ,显示一个进程的线程运行信息列表。 查看堆栈,找到线程在干嘛。jstack 10765 | grep ‘0x2a34’ -C5 --color 日志+gui工具 2.linux环境下,对于一个大文件,如何查看其中某行的内容 通过sed打印指定行号的内容 sed -n 'xp' xxxx.xxx 通过sed打印某个范围内的内容 sed -n 'x,yp' xxx.xxx 3.查看进程状态ps,查看cpu状态 top。查看占用端口的进程号netstat grep |
TCP四次挥手讲一下过程,最后一次ack如果客户端没收到怎么办。 | |
常见业务SQL:
中间件常见英文解释:
缩写 | 含义 |
LEO | Log End Offset。日志末端位移值或末端偏移量,表示日志下一条待插入消息的 位移值。举个例子,如果日志有 10 条消息,位移值从 0 开始,那么,第 10 条消息的位 移值就是 9。此时,LEO = 10。 |
LSO | Log Stable Offset。这是 Kafka 事务的概念。如果你没有使用到事务,那么这个 值不存在(其实也不是不存在,只是设置成一个无意义的值)。该值控制了事务型消费 者能够看到的消息范围。它经常与 Log Start Offset,即日志起始位移值相混淆,因为 有些人将后者缩写成 LSO,这是不对的。在 Kafka 中,LSO 就是指代 Log Stable Offset。 |
AR | Assigned Replicas。AR 是主题被创建后,分区创建时被分配的副本集合,副本个 数由副本因子决定。 |
ISR | In-Sync Replicas。Kafka 中特别重要的概念,指代的是 AR 中那些与 Leader 保 持同步的副本集合。在 AR 中的副本可能不在 ISR 中,但 Leader 副本天然就包含在 ISR 中。关于 ISR,还有一个常见的面试题目是如何判断副本是否应该属于 ISR。目前的判断 依据是:Follower 副本的 LEO 落后 Leader LEO 的时间,是否超过了 Broker 端参数 replica.lag.time.max.ms 值。如果超过了,副本就会被从 ISR 中移除。 |
HW | 高水位值(High watermark)。这是控制消费者可读取消息范围的重要字段。一 个普通消费者只能“看到”Leader 副本上介于 Log Start Offset 和 HW(不含)之间的 所有消息。水位以上的消息是对消费者不可见的。关于 HW,问法有很多,我能想到的 最高级的问法,就是让你完整地梳理下 Follower 副本拉取 Leader 副本、执行同步机制 的详细步骤。这就是我们的第 20 道题的题目,一会儿我会给出答案和解析。 |
consumer_offsets | 要作用是负责注册消费者以及保存位移值。可能你对保存位移值的功能很熟悉, 但其实该主题也是保存消费者元数据的地方。 |
flush | MySQL 刷脏页(flush) |
mysql连坐机制 | MySQL 的“连坐”机制是指当 MySQL 刷脏页的时候如果发现相邻的数据页也是脏页也会一起刷掉,而这个动作可以一直蔓延下去 |
WAL 技术 | 预写式日志,是先写日志,再写磁盘的方式 |
http请求码 | SYN表示建立连接, FIN表示关闭连接, ACK表示响应, PSH表示有 DATA数据传输, RST表示连接重置。 |
数字签名 | 自己私钥对摘要加密,生成的东西叫“数字签名” |
数字证书 | 数字证书是证书中心(CA,certificate authority)为公钥颁发的凭证。证书中心(CA)用自己的私钥,对发件人的公钥和一些相关信息一起加密,生成"数字证书"(Digital Certificate) |
孤儿进程 | 如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程,这是子进程的父进程就是init进程(1号进程) |
僵尸进程 | 如果一个进程已经终止了,但是其父进程还没有获取其状态,那么这个进程就称之为僵尸进程.僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态可以提供父进程想要的信息.一旦父进程得到想要的信息,僵尸进程就会结束. |
守护进程 | Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。 |
父子进程 | 由fork()函数创建的新进程被称为子进程.fork()函数被调用一次,但返回两次,两次的返回值不同,子进程的返回值是0,父进程的返回值是新进程的进程ID. |
fork() | 在用户空间将复制父进程用户空间所有数据(代码段、数据段、BBS、堆、栈),复制父进程内核空间PCB中的绝大多数信息.子进程从父进程继承下例属性:有效用户、组号、进程组号、环境变量、对文件的执行时关闭标志、信号处理方式设置、信号屏蔽集合、当前工作目录、根目录、文件模式掩码、文件大小限制和打开的文件描述符 |
promotion(升级失败) failed | 该问题是在进行Minor GC时,Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下造成的。(promotion failed时老年代CMS还没有机会进行回收,又放不下转移到老年代的对象,因此会出现下一个问题concurrent mode failure,需要stop-the-wold 降级为GC-Serail Old)。 |
concurrent(并发) mode failure | 该问题是在执行CMS GC的过程中同时业务线程将对象放入老年代,而此时老年代空间不足,或者在做Minor GC的时候,新生代Survivor空间放不下,需要放入老年代,而老年代也放不下而产生的。 解决方案: 也就是CMS在进行5次Full GC(标记清除)之后进行一次标记整理算法,从而可以控制老年代的碎片在一定的数量以内,甚至可以配置CMS在每次Full GC的时候都进行内存的整理。 |
TPS | 每秒执行的事务数量. |
多态 | 一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。继承、重写、向上转型 |
范式 | 第一范式:强调的是列的原子性,即列不能够再分成其他几列 第二范式:没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。 第三范式:任何非主属性不依赖于其它非主属性[在2NF基础上消除传递依赖] BCNF:主键内不存在传递依赖 第四范式:只存在一个一对多情况 |
wait(1000) | 1000ms时,自动唤醒 |
刷盘 | MySQL从buffer pool中将内容写到系统的page cache中,并没有持久化到系统磁盘上。这个速度其实是很快的。 |
mysql | 从系统的cache中将数据持久化到系统磁盘上。这个速度可以认为比较慢,而且也是IOPS升高的真正原因。 |
rwnd | 接收端窗口:接收端缓冲区大小,表示接收方的接收能力。接收端将此窗口值放在 TCP 报文的首部中的窗口字段,传送给发送端。 |
cwnd | 发送端缓冲区大小 |
swnd | 发送窗口在连接建立时由双方商定。但在通信的过程中,接收方可根据自己的资源情况,随时动态地调整对方的发送窗口上限值(可增大或减小)。发送窗口的上限值 = Min [rwnd, cwnd] |
MSL | Maximum Segment Lifetime 最长报文段寿命:它是任何报文在网络上存在的最长的最长时间,超过这个时间报文将被丢弃。 |
purge线程 | 清理del flag标签的记录、清理undo的历史版本 |
tcp粘包 | 两个数据包太小,间隔时间短,发送时候合并成一个包 |
tcp拆包 | 数据包太大,超过缓存区,将数据包拆成两个大小的包 |
tcp拆包/粘包 | packet1 太大,将packat1拆分为两个包,拆分后的包又合并到packet2上 |
软技能常见题:
常见问题 | 自己回答 |
怎么解决项目的困难,怎么沟通 | |
项目规划和如何推进 |
脑筋开放题:
题目 | 方案 |
四辆小车,每辆车加满油可以走一公里,问怎么能让一辆小车走最远 | 1.先一起走 2.走一段路程 s 之后,任选一辆车,把剩余的油全分给其余的车,加满 3.重复步骤二,直到最后一辆车没油 s的取值: ans=(1/1+1/2+…+1/n)*x |
十亿个数的集合和10w个数的集合,如何求它们的交集。 | 后来面试官说是对小数组做hash,然后遍历大数组即可。 |
10g文件,只有2g内存,怎么查找文件中指定的字符串出现位置 | 他说可以用cat | grep 管道处理 GNU grep使用原生的输入系统调用,而不用read,避免内存拷贝。 |
考虑某个子树深度特别大的情况,于是我用遍历的方式刷新最大值,用上面那个方法遍历完整个树即可。 |
有坑的注意点:
java 常用代码
public class HelloWord {
public static void main(String[] args) {
String pass = input.next();// 获取用户输入的密
int length = pass.length();
String[][] data = new String[2][5];
int length = data.length;
int size = list.size();
int size = Stack.size();
}
}
public class Dog {
String color ;
int size;
ArrayList<Integer> list = new ArrayList<Integer>();
Character a = new Character('a');
HashMap<String, Float> map = new HashMap<String, Float>();
HashSet<String> set = new HashSet<String>();
Stack<Integer> stack = new Stack<Integer>();
LinkedList<Integer> queue = new LinkList<Integer>();
}
public int insertData(ArrayList<Integet> list) {
return -1;
}
go 常见代码:
func main () {
}
type dog struct {
name string
age int
list []int
}
场景代码总结:
思路总结:
题目总结 | 思路简述 |
常见写法 | go 初始化数组 java struct 排序: 排序go: 排序java: |
最长回文 | 中心拓展, i= i或者i ,i+1 |
LRU算法 | List+HashMap:list维护key的顺序,hash维护是存在<key,value> add:hash中判断是否存在,不存在,先判断是否操作最大值,触发list中移除最早未使用的key,hasmap移除最早未使用的key,最后直接put到map中、list中添加。存在,则list中先删除key,然后在添加key,hashmap在push对应的key。 get:先判断hash中是否存在,存在先获取key,list先删除,在add对应的key |
二分查找判断low和high | 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 |
堆排序 | |
三数之和 | 排序好数组,双指针 |
给定一个数组arr,返回arr的最长无的重复子串的长度。 | for (int i = 1; i < arr.length; i++) { if (object.get(arr[i]) != null ) { max = max > i - pre ? max : i - pre; pre = Math.max(object.get(arr[i]) + 1,pre); } object.put(arr[i], i); } |
A数组归并B数组 | 考虑逆向归并 ,参考 |
公共子列 | 最长公共子序列(LCS):str1[i] == str2[j] : f(i,j) = f(i-1,j-1) + 1 || str1[i] != str2[j]:f(i,j) = max{f(i-1,j),f(i,j-1)} 最长公共子串:str1[i] == str2[j] : f(i,j) = f(i-1,j-1) + 1 || str1[i] != str2[j]:f(i,j) = 0; |
获取平方根 | 连续n个奇数相加的结果一定是平方数 |
树相关的经典题 | 对称: 最近父节点: 重建二叉树: |
链表反转 | 采用头插法反转 |
二叉树最大路径和 | DFS //dfs获取最大的链路 |
完全二叉树 | 层序遍历,完全二叉树在遇到空节点之后剩余的应当全是空节点 |
第K大时候,考虑 | k的上下界 |
全排序 | 跳出的边界是:from = to |
异或(相同为0) | 给定一个无序数组arr,找到数组中未出现的最小正整数 先异或求和,在异或清零 |
阻塞队列的实现方式 | |
手写简单hashmap | |
hashMap根据value排序 | ArrayList<Map.Entry<Integer,String>> list = Collections.sort(list, Comparator.comparing(Map.Entry::getValue)); |
使用拓扑排序来解决这个问题,一直删除出度为0的顶点直到没有出度为0的顶点,如果最终还有顶点存在就说明有环,并且是由剩下的顶点组成的环。 | |
循环队列设计 | private Integer []arr; private int head; private int tail; |
堆排序:
package sortdemo;
import java.util.Arrays;
/**
* Created by chengxiao on 2016/12/17.
* 堆排序demo
*/
public class HeapSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr,0,j);//重新对堆进行调整
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int []arr,int i,int length){
int temp = arr[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
/**
* 交换元素
* @param arr
* @param a
* @param b
*/
public static void swap(int []arr,int a ,int b){
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
头插法反转:
public ListNode reverseKGroup (ListNode head, int k) {
ListNode p0 = new ListNode(0);
p0.next = head;
ListNode cur = head;
while(k > 0){
ListNode temp = cur.next;
cur.next = temp.next;
temp.next = cur;
p0.next = temp;
k--;
}
return p0.next;
}
快排:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 将给定数组排序
* @param arr int整型一维数组 待排序的数组
* @return int整型一维数组
*/
public int[] MySort (int[] arr) {
// write code here
if(arr == null){
return arr;
}
quickSort(arr,0,arr.length - 1);
return arr;
}
public void quickSort(int[] arr , int start ,int end){
if(start >= end ){
return;
}
int left = start;
int right = end;
int solder = arr[start];
while(left < right){
while(arr[right] >= solder && left < right){
right--;
}
if(left < right){
arr[left] = arr[right];
}
while(arr[left] < solder && left < right){
left++;
}
if(left < right){
arr[right] = arr[left];
}
}
arr[left] = solder;
quickSort(arr,start,left - 1);
quickSort(arr,left+1,end);
}
}
单例模式:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
生产者消费者:
大
2.使用中间件与市场中间件的对比、性能差异、原理
表现:mafka、squallar、美团es、犀牛与市场上其他产品间对比
熟悉原理、进行总结、将其对应的区别梳理清楚
序号 | 问题 | 解释 |
1 | ||
2 |
代码:
import java.nio.channels.UnsupportedAddressTypeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import com.apple.laf.resources.aqua;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
public class HelloWord {
public static void main(String[] args) {
int[] arr = new int[]{1,4,-199,2,4};
int[] result = maxRange(arr);
System.err.println(result);
HelloWord helloWord = new HelloWord();
ListNode cur4 = helloWord.new ListNode(4);
ListNode cur3 = helloWord.new ListNode(3, cur4);
ListNode cur2 = helloWord.new ListNode(2, cur3);
ListNode cur1 = helloWord.new ListNode(1, cur2);
helloWord.reverseMnGroup(cur1, 1, 2);
Trie tr = helloWord.new Trie();
tr.insert("good");
System.err.println(tr.search("goo"));
quickSort(arr, 0, arr.length - 1);
System.out.println(arr.toString());
for (int i = 0; i < 10; i++){
Consumer consumer = helloWord.new Consumer();
new Thread(consumer).start();
Producer producer = helloWord.new Producer();
new Thread(producer).start();
}
}
public static int[] maxRange(int[] arr ) {
//dp[i] = max(dp[i-1], arr[i])
int[] startArray = new int[arr.length];
int[] dp = new int[arr.length];
dp[0] = arr[0];
startArray[0] = 0;
for(int i = 1; i < arr.length; i++){
if (dp[i-1] > 0){
dp[i] = dp[i-1] + arr[i];
startArray[i] = startArray[i-1];
}else{
dp[i] = arr[i];
startArray[i] = i;
}
}
int max = dp[0];
int start = 0;
int end = 0;
for(int i = 1; i < arr.length; i++){
if (max < dp[i]){
max = dp[i];
start = startArray[i];
end = i;
}
}
int[] result = new int[end - start +1];
for(int i = start; i <= end; i++){
result[i - start] = arr[i];
}
return result;
}
class Trie {
Trie[] tries;
boolean isEnd;
public Trie(){
tries = new Trie[26];
}
public void insert(String value){
if(value == null || value.length() == 0){
return;
}
Trie cur = this;
for(int i = 0; i < value.length(); i++){
if(cur.tries[value.charAt(i) - 'a'] == null){
cur.tries[value.charAt(i) - 'a'] = new Trie();
}
cur = cur.tries[value.charAt(i) - 'a'];
if(i == value.length() - 1){
cur.isEnd = true;
}
}
}
public boolean search(String value){
Trie cur = this;
for(int i = 0; i < value.length(); i++){
if(cur.tries[value.charAt(i) - 'a'] != null){
cur = cur.tries[value.charAt(i) - 'a'];
if(i == value.length() - 1){
return cur.isEnd;
}
}else{
return false;
}
}
return false;
}
}
//快排
public static void quickSort(int[] arr, int start, int end){
if (start >= end){
return;
}
int left = start;
int right = end;
int solder = arr[left];
while(left < right){
while(left < right && arr[right] >= solder) {
right--;
}
if (left < right) {
arr[left] = right;
}
while (left < right && arr[left] < solder){
left++;
}
if (left < right) {
arr[right] = arr[left];
}
}
arr[left] = solder;
quickSort(arr, start, left - 1);
quickSort(arr, left + 1, end);
}
//生产者消费者
public static int count = 0;
public static final int FULL = 10;
public static String lock = "lock";
public class Producer1 implements Runnable {
@Override
public void run(){
try{
Thread.sleep(10000);
}catch(Exception e){
e.printStackTrace();
}
synchronized(lock){
while(count == FULL){
try{
lock.wait();
}catch(Exception e){
e.printStackTrace();
}
}
count++;
lock.notifyAll();
}
}
}
public class Consumer1 implements Runnable{
@Override
public void run(){
try{
Thread.sleep(10000);
}catch(Exception e){
e.printStackTrace();
}
synchronized(lock){
while(count == 0){
try{
lock.wait();
}catch(Exception e){
e.printStackTrace();
}
}
count--;
lock.notifyAll();
}
}
}
public class Producer implements Runnable {
@Override
public void run() {
try {
Thread.sleep(3000);
}catch(Exception e) {
throw new UnsupportedOperationException("Unimplemented method 'run'");
}
synchronized(lock){
while(count == FULL) {
try{
lock.wait();
}catch(Exception e) {
throw new UnsupportedOperationException("messagfe");
}
}
count++;
System.out.println(Thread.currentThread().getName() + "生产者生产,目前总共有" + count);
lock.notifyAll();
}
}
}
public class Consumer implements Runnable {
@Override
public void run() {
try{
Thread.sleep(3000);
}catch(Exception e) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'run'");
}
synchronized(lock){
while(count == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName() + "消费者消费,目前总共有" + count);
lock.notifyAll();
}
}
}
//string 转 int
public int StringToInt(String s) {
//去除空格
//截取只有数字部分
//判断是正数还是负数
//超过最大值处理
return 0;
}
public static class Person {
int age;
}
//全排序
public void allSort(int[] array, int start) {
if (start == array.length) {
System.err.println(array);
}
for (int i = start; i < array.length; i++){
swap(array, i, i);
allSort(array, start + 1);
swap(array, i, i);
}
}
public void swap(int[] array, int i, int j) {
int temp = i;
array[i] = array[j];
array[j] = array[i];
}
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
// 12345
public ListNode reverseMnGroup(ListNode head, int m, int n){
//cut
//reverse
//merge
ListNode preHead = new ListNode();
preHead.next = head;
ListNode cur = preHead;
int position = 0;
while(cur != null){
if(position == m - 1 && cur.next != null){
ListNode pre = cur;
// head add
cur = cur.next;
ListNode tail = cur;
while(cur != null && position < n){
ListNode curNext = cur.next;
cur.next = pre.next;
pre.next = cur;
cur = curNext;
position++;
}
tail.next = cur;
}
cur = cur.next;
position++;
}
return preHead.next;
}
public static void merge2(int[] arr, int low, int mid , int high){
if(low >= high){
return;
}
int i = low;
int j = mid+1;
int k = 0;
int[] temp = new int[arr.length];
while(i <= mid && j <= high){
if(arr[i] < arr[j]){
temp[k++] = arr[i++];
}else{
temp[k++] = arr[j++];
}
}
}
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 把较小的数先移到新数组中
while (i <= mid && j <= high) {
if (a[i] < a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while (j <= high) {
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for (int k2 = 0; k2 < temp.length; k2++) {
a[k2 + low] = temp[k2];
}
}
public static void mergeSort(int[] a, int low, int high) {
if(low >= high){
return;
}
int mid = (low + high) / 2;
// 左边
mergeSort(a, low, mid);
// 右边
mergeSort(a, mid + 1, high);
// 左右归并
merge(a, low, mid, high);
}
// 二分、归并、快排是基础
//常见的长度获取、compare、最大值表
public void normal(){
List<List<Integer>> allCompoent = new ArrayList<ArrayList<Integer>>();
Stack<String> stack = new Stack<String>();
//数组、字符串、list 长度
int[] arr = new int[]{};
System.out.println(arr.length);
ArrayList<Integer> list = new ArrayList<Integer>();
System.out.println(list.size());
String s = "abcdefg";
System.err.println(s.length());
Character c = '1';
System.out.println(s.length());
//最大值、最小值
int max = Integer.MAX_VALUE;
int min = Integer.MIN_VALUE;
//排序
Collections.sort(list);
//比较排序
ArrayList<Person> persons = new ArrayList<>();
Collections.sort(persons, new Comparator<Person>() {
@Override
public int compare(HelloWord.Person o1, HelloWord.Person o2) {
return o1.age - o2.age;
}
});
List<int[]> value = new ArrayList<int[]>();
Collections.sort(persons, (Person p1, Person p2) ->{
return p1.age - p2.age;
});
Integer[] array = new Integer[5];
Arrays.sort(array, ( Integer a, Integer b) -> {return a-b;});
Set<Integer> row = new HashSet<Integer>();
Integer[] rowArray = new Integer[row.size()];
row.toArray(rowArray);
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.remove(1);
value.set(min, arr);
}
}
todo:一些总结
1. Go map相关的原理。
2. GMP的模式。
3. 项目的架构图,如何说项目架构还是欠缺。多想想怎么说?
4. go和java写起来还是不够熟练。
5. 自我介绍感觉有点干。