【涛桑java面试突击】

该博客围绕Java开发面试展开,涵盖简历撰写、面试流程,详细介绍Redis、集合、JVM、多线程、框架、微服务、消息中间件、数据库等技术的面试要点及解决方案,还提及企业场景中的设计模式和技术场景应用,为Java开发者面试提供全面指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

涛桑java面试突击

简历篇1

  • Hr关注点:学历、院校、经验、年龄、跳槽频率
  • 技术Hr关注点:技术、业务、加分项(管理经验,高并发,公有云,博客)
  • 简历注意事项:
    1.个人技能放C位
    2.写在简历的必须能聊
    3.引导面试官问你会的
  • 面试官角色
    1.资深开发/技术经理:技术最好,参与首轮,决定候选人
    2.业务部门经理:技术一般,参与终面,决定薪资(思考能力、抗压能力
    3.HR:辅助考察:性格,沟通,合作,学习等能力
  • 面试过程
    1.自我介绍:我叫涛桑,我的工作经历主要分为3个阶段,巴拉巴拉
    2.项目介绍:业务深度,难点,引导接下来的面试方向

Redis篇

使用场景(根据简历业务回答-引导!)

  • 缓存:
    1.穿透、击穿、雪崩
    2.双写一致、持久化
    3.数据过期、淘汰策略
  • 分布式锁
    1.setnx、redisson
  • 计数器:INCR自增
  • 保存token:数据类型string
  • 消息队列:数据类型list
  • 延迟队列:数据类型zset

【缓存穿透】:访问数据库不存在的数据,虚无缥缈,仿佛透过缓存的身体一直查数据库,多发生在恶意接口访问

  • 解决方案1:缓存空数据,查到为空的把null存到缓存中
    • 优点:简单
    • 缺点:消耗内存,可能发生不一致的情况
  • 解决方案2:布隆过滤器:数组+多组hash函数计算是否存在(判断key是否存在)
    • 优点:内存占用少,没有多余key
    • 缺点:实现复杂,存在误判率(某个不该存在的key的多次hash均命中其他key的hash结果
    • 实现原理
      • 1.更新redis同时将key存储在布隆过滤器中,将key通过多次不同的hash计算将结果0/1存放于数组中
      • 2.判断key是否存在,使用多次相同的hash计算判定结果对应位置是否都为1

【缓存击穿】:某个热点 key过期时间到了,且恰好此时有大量并发请求,瞬间击穿DB

  • 解决方案1:互斥锁:缓存重建时设置互斥锁,其他请求等待知道重建成功
    • 特点:强一致,性能差
  • 解决方案2:逻辑过期:热点数据不设置过期时间,改为新增过期时间字段(命中过期则返回过期+新建线程重建缓存
    • 特点:高可用,性能优,不保证绝对一致

【缓存雪崩】:大量/同时失效,或redis宕机,顾名思义,雪崩

  • 解决方案1:给不同的key的过期时间添加随机值(业务中高频节点录入的数据)
  • 解决方案2:利用redis集群的高可用性:哨兵模式、集群模式
  • 解决方案3:保底策略业务添加降级限流策略:nginx或cloud的gateway
  • 解决方案4:多级缓存Guava或Caffeine

【缓存一致性】:修改数据库后,同步更新缓存数据,保持数据一致性

  • 问:redis作为缓存,数据库是如何与redis进行同步的呢?
  • 答:首先一定介绍业务背景,(一致性要求高 / 允许延迟一致)

双写一致性级别:

  • 最拉胯:直接删缓存(删缓存 - 修改数据库)
  • 普通:延迟双删:(删缓存 - 修改数据库 - (延时)再删缓存 )
  • 延时一致
    1.MQ中间件,更新数据后,通知缓存删除
    2.阿里canal组件,伪装mysql从节点,读取binlog更新缓存
  • 强一致:(性能不高)
    1.分布式锁
    2.Redisson读写锁:读readLock共享锁,可一起读,不能写;
    写writeLock排他锁,不可同时读写

【缓存持久化】:RDB+AOF

RDB(Redis Database Backup file)

redis数据备份文件,数据快照,即将内存中所有数据记录到磁盘中,故障重启时读磁盘恢复

主动备份

[root@localhost ~]# redis-cli
127.0.0.1:6397> save #redis主线程执行,阻塞所有命令
ok
127.0.0.1:6397> bgsave #redis子线程执行,避免主线程受影响
Background saving started

自动备份:redis.conf文件中

#900秒内,至少有一个key被修改,则执行bgsave
save 900 1
save 300 10
save 60 10000

执行原理

  1. fork主进程得到子进程,子进程共享主进程的内存数据(页表:记录虚拟地址与物理地址的映射关系)
  2. 子进程读取内存数据写入RDB文件中覆盖旧的RDB文件
  3. fork采用copy-on-write(COW奶牛技术)技术:当主进程读操作时使用共享内存,写操作时拷贝一份数据执行写
AOF(Append Only File追加文件)

redis处理每一个写命令都增量记录到AOF文件中,可以看做是命令日志文件,默认关闭

配置redis.conf(是否开启AOF丨配置刷盘频率 丨 配置bgrewriteaof重写)

#是否开启AOF功能,默认no
appendonly yes
#AOF文件名称
appendfilename "appendonly.aof"


#配置刷盘频率
appendfsync always     #立即执行    丨 可靠性高,几乎不丢数据 丨 性能影响大
appendfsync everysec   #每秒执行    丨 最多丢失1秒数据       丨  性能适中(默认)
appendfsync no         #操作系统控制丨 可靠性较差,可能丢失数据 丨 性能最好   


#配置bgrewriteaof重写规则,相同key多次写操作只保留最后一次,不然文件太大  
#AOF文件比上次文件增长了多少百分比则触发重写
auto-aof-rewrite-percentage 100
#AOF文件体积超过多少则触发重写
auto-aof-rewrite-min-size 64mb   
RDB与AOF对比
RDBAOF
持久化方式定时对整个内存快照增量记录每次写命令
数据完整性不完整,两次备份会丢失相对完整,取决于刷盘策略
文件大小会有压缩,体积小记录命令,体积大
宕机恢复速度很快
数据恢复优先级低,因为数据完整性不如AOF高,因为完整性更高
系统资源占用高,大量CPU占用和内存消耗低,主要都是磁盘IO(重写会占CPU和内存)
使用场景容忍分钟数据丢失,追求启动速度对数据安全性要求较高

【Redis数据过期策略】(惰性删除+定期删除 配合使用 )

  • 问:redis的key过期后,会立即删除吗?(考察redis过期策略
  • 答:redis设置数据有效时间过期后,会按照不同的规则从内存中删除,这种删除策略包括惰性删除定期删除
  • 惰性删除:访问key时再检查是否过期,过期则删
    • 优点:对CPU友好,不会浪费时间在用不到的key的过期检查上
    • 缺点:对内存不友好,过期不用的key会一直存在
  • 定期删除:定期对key进行检查,删除过期key(从一定数量的数据库中随机检查)
    • SLOW模式:定时任务,执行频率默认10hz,每秒10次,每次不超过25ms,可以通过redis.conf配置文件的hz来调整次数
    • FAST模式:执行频率不固定,但两次间隔不低于2ms,每次不超过1ms(尽量少的占用主进程的操作)
    • 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响,也可以定期释放内存
    • 缺点:难以确定删除操作执行的时长和频率

【Redis数据淘汰策略】(默认noeviction,不淘汰,满了就报错

  • 问:假如缓存过多,内存占满了怎么办?(考察redis淘汰策略
  • 答:redis内存不够用时,此时再添加新的key时,redis会按照某种规则将内存中的数据删除掉,这种规则称之为淘汰策略,redis支持8种不同的策略,默认是noeviction,就是不淘汰,满了则报错

配置maxmemory-policy noeviction

配置项规则使用建议
noeviction不淘汰,满了则报错,不允许写入(默认)
volatile-ttl对设置了TTL的key,所剩不多的先被淘汰
volatile-random对设置了TTL的key,随机淘汰
volatile-lru对设置了TTL的key,基于LRU算法淘汰置顶需求
volatile-lfu对设置了TTL的key,基于LFU算法淘汰短时高频访问
allkeys-lru对全体key,基于LRU2算法淘汰(近期不热就被淘汰)有明显冷热数据区分
allkeys-lfu对全体key,基于LFU2算法淘汰(热度不高就被淘汰)短时高频访问
allkeys-random对全体key,随机淘汰没有明显冷热数据区分

【Redis分布式锁】

  • 使用场景(集群):库存、幂等、定时任务(统计定时修复任务,出过问题会修复多次)
  • setnx:redis的setnx命令,set if not exists(如果不存在,则set)的简写
    • 获取锁:set lock1 value1 NX EX 10(写到一条命令是为了原子性,ex是过期时间,不设置过期时间会导致宕机后无法释放锁导致死锁)
    • 释放锁:del lock1
  • Redisson:底层是setnx+lua脚本(保证原子性)
  • 问1:redis实现分布式锁如何合理的控制锁的有效时长
  • 答1:redisson实现的分布式锁的Watch dog看门狗技术,可以监听时长并续期,默认10s续期一次
  • 问2:redisson是可重入锁吗
  • 答2:是可重入,多个锁重入判定当前线程,redis中使用hash结构存储着线程信息和重入的次数
  • 问3:redisson可以解决主从数据一致的问题吗
  • 答3:不能解决,但是可以使用redisson提供的红锁来解决,但是性能太低,如果业务中非要保证强一致性,建议采用zookeeper实现的分布式锁,redis分布式锁满足AP3原则, zookeeper实现的分布式锁满足CP原则。

【Redis集群模式】

主从复制:为提高单点并发能力,一主多从,主写从读。
  • 全量同步:
    • 1.从节点启动时候请求主节点同步(发送repid+offset)
    • 2.主节点通过repid判断是否是第一次,第一次则同步repid+offset信息
    • 3.执行bgsave命令生成rdb快照发送给从节点执行
    • 4.复制过程中将新增写命令记录到repl_baklog文件中
    • 5.把生成之后的repl_baklog文件发送给从节点同步
  • 增量同步:
    • 1.从节点重启或定时(默认10s,可配置)请求主节点同步repid+offset
    • 2.主节点根据repid判断是否是第一次,不是第一次则获取从节点的offset
    • 3.主节点从repl_baklog文件中获取offset之后的数据发送给从节点同步
哨兵模式:为实现高可用!,主从集群的自动故障恢复
  • 作用
    • 监控:Sentinel(哨兵)会不断检查master和slave是否按预期工作
      • 所有哨兵每1秒(默认)向所有实例发送ping命令
      • 当有一个哨兵没有收到回应,判定你主观下线
      • 当有一半哨兵没有收到回应,判定你客观下线(一半哨兵数量quorum可配置)
    • 自动故障恢复:如果master故障。哨兵会将一个salve提升为master,故障恢复后以新的为主
    • 通知:当集群发生故障master转移后,哨兵给客户端推送最新消息
  • 流程
    1.哨兵判定主节点挂了
    2.多数哨兵判定主节点挂了
    3.哨兵推举哨兵头子
    4.哨兵头子筛选salve并推举为master
    5.哨兵头子通知其他哨兵及其他从节点,改朝换代啦
    6.哨兵头子通知客户端,以后请连接新主子
    7.旧主子回来时候自动作为小弟归位
  • 改朝换代标准
    • 首先判定该salve与主节点断开时长,太长的不要
    • 然后判定salve-priority配置,是否钦点,没有或一样的话继续
    • 判定salve的offset大小,即存储量,谁多要谁
    • 最后判定运行id,推举最小的,即最年轻
  • 哨兵脑裂
    • 主节点网络波动,哨兵断感,客户端没有断,哨兵误判推举出一个新的主节点,此时客户端数据还是会写入旧的主节点,当旧主回来直接沦为从节点(清空数据并同步主节点数据),此时会丢失中间写入的数据
    • 解决方案:redis.conf设置最少从节点数量或同步延迟时间,断感的旧主不满足配置则会自动拒绝请求
min-replicas-to-write 1 #表示最少的salve节点为1个
min-replicas-max-lag 5  #表示数据复制和同步的延迟不能超过5s
分片集群:为实现海量数据存储及高并发写的问题
  • 集群有多主多从,每个master保存不同的数据
  • master之间监控心跳,类似哨兵
  • 客户端可以访问集群中任意节点,最终都会路由到正确的节点
    • redis集群中16384个哈希槽,每个节点负责一部分
    • 0-5461-10922-16383不同实例
    • key通过CRC16校验后对16385取模决定在哪个槽里

其他面试题

  • 事务----multi/discard
  • 数据结构

redis为什么快

  • 1.完全基于内存,C语言编写
  • 2.采用单线程,避免上下文切换,6.0之后采用操作单线程+解析多线程
  • 3.采用I/O多路复用模型
    • redis速度瓶颈不在内存而在网络IO,传统阻塞I/O比较浪费时间,redis采用多路复用技术同时监听多个socket连接(redis多个客户端,之前靠轮询等待),当有读写需求时再去调用系统IO读写操作
      在这里插入图片描述

集合篇

算法复杂度分析

  • 问:什么是算法时间复杂度
  • 答:时间复杂度表示算法的执行时间与数据规模之间的增长关系
  • 问:什么是算法空间复杂度
  • 答:空间复杂度表示算法的存储空间与数据规模之间的增长关系

在这里插入图片描述

List

数组

int[] array = {0,1,2,3,4}

  • 数组(Array)是一种连续的内存空间存储相同数据类型数据的线性数据结构
  • 寻址公式(类比分页):array[1] = baseAddress + i * dataTypeSize
    • baseAddress:数组的首地址
    • dataTypeSize:数组中元素类型的大小,int型的4字节

【ArrayList】

List list = new ArrayList();
list.add(1);

  • 问:ArrayList底层的实现原理是什么?
  • ArrayList底层使用动态数组实现的
  • ArrayList初始容量为0第一次添加数据时初始化为10
  • ArrayList进行扩容的时候是原来的1.5倍,每次扩容都需要拷贝数组Arrays.copyOf()
  • ArrayList在添加数据时:
    • 确保数组已使用长度size足够
    • 计算数组容量,不足时调用grow方法扩容为原来的1.5倍
    • 将新元素添加到size的位置上
    • 返回添加成功boolean
  • 问:如何实现数组和List之间的转换
  • 数组转list(转换后修改数组会影响list):List list = Arrays.asList(strs);
  • list转数组(转换后修改list不会影响数组):String[] array = list.toArray(new String[list.size()]);

【LinkedList】

  • 单向链表:非连续、非顺序的存储结构,每个元素称之为结点(Node:包含data+next)
  • 双向链表:非连续、非顺序的存储结构,每个元素称之为结点(Node:包含data+next+prev),支持双向遍历

ArrayList和LinkedList的区别是什么?

  • 1.底层数据结构
    • ArrayList是动态数组的数据结构实现
    • LinkedList是双向链表的数据结构实现
  • 2.操作数据效率
    • ArrayList根据下标按照寻址公式查,LinkedLinst不支持下标查询
    • 查找(未知索引):ArrayList需要遍历,LinkedList也需要链表遍历,时间复杂度都是O(n)
    • 新增和删除
      • ArrayList尾部插入和删除快(O(1));其他部分删除需要挪动数组慢(O(n))
      • LinkedList头尾增删快(O(1));其他需要遍历链表慢(O(n))
  • 3.内存占用
    • ArrayList底层是数组,内存连续,节省内存
    • LinkedList双向链表需保存data+next+prev,更占内存
  • 4.线程安全
    • 都不安全
    • 如果需要线程安全有两种方案
      • 在方法内使用,局部变量则是线程安全的
      • 使用线程安全的ArrayList和LinkedList
List<Object> syncArrayList = Collections.synchronizedList(new ArrayList<>());
List<Object> syncLinkedList = Collections.synchronizedList(new LinkedList<>());

HashMap

【二叉树】(链表衍生)

  • 二叉树:每个节点最多两个"叉",每个节点的左右子树也满足二叉树的定义
  • 二叉搜索树(Binary Serach Tree,BST)又名二叉搜索树,有序二叉树:
    • 树中任意节点的左子比本身小,右子比本身大
    • 没有键值相等的节点
    • 二叉搜索树的时间复杂度为O(logn)

【红黑树】(特殊的二叉树,二叉树有极限偏科情况,红黑树保证平衡)

  • 红黑树(Red Black Tree):也是一种自平衡的二叉搜索树(BST),之前叫平衡二叉B树(Symmetric Binary B-Tree)
  • 特质(保证平衡不偏科的限定)
    • 节点要么红色要么黑色,根节点是黑色
    • 所有叶子节点都是黑色的null
    • 红色节点的子节点都是黑色
    • 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点
  • 红黑树的查找、添加、删除的时间复杂度都是O(logn)

【散列表(Hash Table)】(数组演化)

  • 散列表,又名哈希表/Hash表,是根据键key直接访问内存存储位置的值value的数据结构,数组演化而来,利用了下标寻址的特性
  • 散列函数,将key映射为数组下标的函数。hashIndex = hash(key)
    • 散列函数计算的散列值必须为**>=0的正整数**,因为需要作为下标
    • 如果key1==key2,那么hash(key1) == hash(key2)
    • 如果key1!=key2,那么hash(key1)!=hash(key2)(不太好实现,会发生哈希冲突)
  • 散列冲突:又叫哈希冲突,哈希碰撞,指多个key映射到同一个数组下标的位置
  • 散列冲突-拉链法
    • 数组每个下标位置称之为桶(bucket)或者槽(slot)
    • 每个桶对应一个链表
    • 冲突的key以链表形式追加
    • 链表过长时为方便查询会转为红黑树

HashMap的实现原理

  • HashMap的底层数据结构为散列表+链表/红黑树
  • 添加数据put时,计算key的hash值确定数组下标
    • key相同则替换
    • key不相同则存入链表或红黑树中
  • 获取数据get时,计算key的hash值确定数组下标获取元素

【HashMap的jdk1.7和jdk1.8有什么区别】

  • jdk1.8之前采用的拉链法:数组+链表
  • jdk1.8之后采用数组+链表+红黑树链表长度大于8数组长度大于64则会从链表转化为红黑树(两个条件须都满足,不满足64则会触发一次扩容),在resize扩容时,如果红黑树节点数小于6会退化为链表
  • jdk1.7扩容链表采用头插法,jdk1.8扩容链表改为尾插法

【HashMap的put方法的具体流程】

  • 1.判断数组table是否为空桶或为null,执行resize()扩容2倍

默认初始容量DEFAULT_INITIAL_CAPACITY值为16
默认加载因子DEFAULT_LOAD_FACTOR值为0.75
扩容阈值(threshold)=数组容量 * 加载因子,
懒加载初始化,Map<String,String> map = new HashMap<>();
此时并没有初始化数组,只是初始化了默认加载因子,第一次put的时候才会进行初始化数组

  • 2.根据key计算hash值,确定数组下标

  • 3.判断table[i]是否为空,为空则直接新建节点放入

  • 4.不为空

    • 判断key跟table[i]的首个元素是否相同,相同则覆盖value
    • 不相同则判断是红黑树 OR 链表(是否为treeNode)
      • 红黑树,则判断红黑树是否存在key,存在覆盖,不存在追加
      • 链表,则判断链表是否存在key,存在覆盖,不存在追加(长度大于8转红黑树)
  • 5.插入成功后,判断**++size > threshold**,如果超过,则resize()扩容
    在这里插入图片描述

【HashMap的扩容机制】:新建数组,均摊数据

  • 在添加元素或初始化的时候需要resize()扩容,第一次初始化数组长度为16,之后每次**达到扩容阈值(数组长度*0.75)**都会进行扩容
  • 每次扩容都是之前的2倍
  • 扩容过程,会新创建一个数组,将老数组的数据均摊到新数组中
    • 如果是没有hash冲突的节点(即不是链表/红黑树的单节点),直接e.hash & (newCap-1)计算新索引位置
    • 如果是红黑树,则红黑树拆分添加至新数组中;//TODO(实际拆分逻辑)
    • 如果是链表,则遍历拆分链表,根据e.hash &oldCap判定新数组的位置

【HashMap其他面试题】

  • 问:hashMap的寻址算法
  • 答:两次hash,为了分布更均匀
  • 1.计算对象的hashCode()
  • 2.再次调用hash()方法进行二次哈希,为了让哈希分布更为均匀(hashcode右移16位再异或运算)
  • 3.最后**(capacity-1) & hash**得到索引
  • 问:为什么HashMap的数组长度一定是2的次幂
  • 答:方便位与运算代替取模
  • 问:HashMap线程安全吗?
  • 答:不安全,
  • 在jdk1.7的hashmap中在数组进行扩容的时候,因为链表是头插法,在数据迁移过程中,有可能导致死循环
  • CurrentHashMap1.7采用了分段锁+unsafe类,保证线程安全
  • 在jdk1.8中,采用尾插法,在并发put操作时会发生数据覆盖
  • CurrentHashMap1.8采用了synchronized,保证线程安全

JVM篇

JVM组成

【JVM组成部分】

在这里插入图片描述

  • 类加载系统:java代码转为字节码
  • 运行时数据区:分配存储空间
  • 执行引擎:字节码翻译为底层系统命令
  • 本地方法接口:C/C++实现,一些系统本地方法
  • 垃圾回收系统:垃圾回收

【什么是程序计数器】

  • 用于记录正在执行的字节码的行号来找下一条指令,线程私有,线程你来我往执行指令

【堆】

  • 定义:线程共享的区域,主要用来保存对象实例,数组等,堆内无内存会报OOM
  • 组成:年轻代:老年代= 1:2eden:survivor1:survivor2 = 8:1:1
    • 年轻代
      • Eden区
      • Survivor1
      • Survivor2
    • 老年代保存老的对象
  • jdk1.7和1.8的区别
    • 1.7有一个永久代,存储类信息、静态变量、常量、编译后的代码
    • 1.8中移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出

【什么是虚拟机栈】

  • 每个线程运行需要的内存,称为虚拟机栈,先进后出
  • 每个栈由多个栈帧(frame)组成,对应着每次方法调用所占用的内存
  • 每个线程只能有一个活动栈帧,对应着正在执行的方法

  • 问:垃圾回收涉及栈内存吗?
  • 答:不涉及,垃圾回收主要指的是堆内存栈帧弹栈以后内存就会释放

  • 问:栈内存分配越大越好吗?
  • 答:未必,默认栈内存通常为1024k,栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个,如果把栈改为2048k,则只能同时有256个线程活动

  • 问:方法内的局部变量是否线程安全**?
  • 如果方法内局部变量没有逃离方法作用范围,则是线程安全
  • 如果是局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全

  • 问:栈内存溢出的情况(java.lang.stackOverflowError)
  • 栈帧过多导致:递归调用
  • 栈帧过大导致

  • 问:堆栈的区别是什么?
  • 1.栈内存一般用来存储局部变量和方法调用,堆内存存放java对象和数组
  • 2.堆会被GC垃圾回收,栈不会
  • 3.栈线程私有,堆线程共有
  • 4.栈空间不足:java.lang.stackOverflowError
  • 5.堆空间不足:java.lang.OutOfMemoryError

【能不能解释下方法区】

  • 方法区(Method Area)是各个线程共享的内存区域
  • 1.7以前在堆中的永久代,1.8以后在本地内存的元空间中
  • 主要存放类的信息,运行时常量池
  • 虚拟机启动时创建,关闭时释放
  • 如果方法区域中的内存无法满足分配请求,抛出OutOfMemoryError:Metaspace

  • 问:介绍一下运行时常量池
  • 常量池.class文件中,记录类名、方法名、参数类型、字面量等信息的表
  • 运行时常量池:当类被加载时,它的常量池信息会被放入运行时常量池,并把里面的符号地址变为真实地址

【你听过直接内存吗?】

  • 并不属于JVM的内存结构,不由jvm管理,是虚拟机的系统内存
  • 常见于NIO操作时,用于数据缓冲区分配回收成本较高,但读写性能高不受jvm内存回收

类加载

【类加载器】:二进制字节码文件 == 》JVM

  • 启动类加载器(Bootstrap):JAVA_HOME/jre/lib
  • 扩展类加载器(Ext):JAVA_HOME/jre/lib/ext
  • 应用类加载器(App):classpath
  • 自定义类加载器:自定义加载规则

【双亲委派】先找父亲,有爷爷找爷爷,没有自己加载

  • 避免类重复加载,保证唯一性
  • 保证安全,API不被修改
  • 不直接从上往下查是为了避免有多个同级自定义加载器无法选择

【类装载的执行过程】

  • 加载
    • 通过类的全名,获取类的二进制数据流
    • 解析成方法区内的数据结构(Java类模型)
    • 创建java.lang.Class类的实例表示该类型,作为方法区各类各种数据的访问入口
  • 连接
    • 验证
      • 文件格式是否错误
      • 语法是否错误
      • 字节码是否合规
      • 符号引用是否存在:Class文件常量池中记录了自己将要使用的类或方法
    • 准备:为类变量分配内存并设置初始值
      • static分配空间+设置默认值(int的0)(赋值在初始化阶段)
      • static+final+基础类型/string分配空间+赋真正值
      • static+final+引用类型分配空间(赋值在初始化阶段)
    • 解析:把类中符号引用转为直接引用
      在这里插入图片描述
  • 初始化:对类的静态变量,静态代码块执行初始化操作(首次访问类的静态变量时)
    • 父类未初始化,先初始化父类
    • 子类访问父类静态变量,只触发父类初始化
    • 多个静态变量和静态代码块,自上而下
  • 使用: 从入口方法开始执行
    • 调用静态变量、静态方法
    • 使用new关键字创建实例
  • 卸载:堆中class对象不再被引用方法区中的二进制数据会被卸载,启动类加载器加载的类不会被卸载

垃圾回收

【如何定位垃圾】

  • 引用计数法:对象被引用的次数,为0则被回收
    缺点:循环引用会引发内存泄漏
  • 可达性分析(默认):扫描对象,判定是否能沿着GC Root找到,找不到则可回收
    • GC Root
      • 虚拟机栈(栈帧中的本地变量表)中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中**JNI(Native方法)**引用的对象

【如何清除垃圾】

  • 标记清除算法(标记+回收)
    • 步骤:
      • 可达性分析进行标记
      • 按标记回收
    • 优点:标记和清除都很快
    • 缺点:内存碎片化
  • 标记压缩算法(标记+回收+压缩):一般老年代
  • 标记复制算法(标记+复制+回收):两块相同的内存空间,一般年轻代
    • 步骤
      • 可达性分析标记
      • 把A存活的放到B,把A清理
      • 把B存活的放到A,把B清理
    • 优点:垃圾多时效率高,内存无碎片
    • 缺点:2块内存只能用1块使用率低
  • 分代回收算法
    • 步骤:
      • 新创建的对象会放在eden区(伊甸园区)
      • eden内存不足,标记
      • eden + A = B
      • eden + B = A
      • 15次后晋升老年代(A或B不足 / 大对象 会提前晋升)

  • 问:MinorGC、MixedGC、FullGC的区别?
  • MinorGC(YoungGC):新生代,暂停时间短(STW4)
  • MixedGC:新生代+老年代部分G1收集器特有
  • FullGC:新生代+老年代完整垃圾回收,STW较长,应尽力避免

【垃圾回收器】

  • 串行垃圾回收器单线程回收,阻塞其他线程
    • Serial:新生代,采用复制算法
    • Serial Old:老年代,采用标记压缩算法
  • 并行垃圾回收器:多线程回收,阻塞其他线程,JDK8默认使用
    • Parallel New:新生代,采用复制算法
    • Parallel Old:老年代,采用标记压缩算法
  • 并发垃圾回收器(CMS):多线程回收,不阻塞其他线程,老年代,标记清除算法
  • G1垃圾回收器:新+老,JDK9默认
    • 划分多个区域:eden + survivor + old + humongous(大对象)
    • 复制算法
    • 响应时间吞吐量兼顾
    • 阶段
      • 新生代回收(阻塞 )eden到阈值就转survivor,次数多了(survivor阈值)到old
      • 并发标记(不阻塞)old多了(45%)
      • 混合收集(阻塞,预期时间)垃圾多的old + survivor阈值 = old
    • 并发失败(回收速度赶不上new速度),会触发FullGC

JVM调优

【堆空间大小】(初始Xms(默认1/46RAM) 丨 最大Xmx(默认1/4RAM))

  • 部署方式
    • war包部署:Tomacat_home/bin/catalina.sh
    • jar包部署:java -Xms512m -Xmx1024m -jar xxxx.jar
  • 调优建议
    • 太小则会频繁GC,STW
    • 太大FullGC太慢
    • 推荐:尽量大,需考察计算器其他程序内存使用情况

【栈空间大小】(初始Xss(默认1M))

  • 存放栈帧,调用参数、局部变量,一般256k够用,-Xss调优

【年轻代/老年代大小】

  • 年轻代默认Eden :Survivor = 8:1:1
    • 调优:
      • -XXSurvivorRatio = 8,Eden占总比
  • 老年代:次数晋升阈值
    • 调优: -XX:MaxTenuringThreshold = threshold

【选择垃圾回收器】

-XX:UseG1GC
-XX:UseParallelGC
-XX:UseParallelOldGC

【调优工具】

  • 命令工具
    • jps:进程状态工具
    • jstack:线程堆栈信息
    • jmap:堆转信息:jmap -dump:format=b,file=e:/aaa/bbb.hprof 56560[pid]
      • vm参数生成
    • jhat:堆转储快照分析工具
    • jstat:JVM统计检测工具
  • 可视化工具
    • jconsloe:用于堆jvm的内存、线程、类的监控(jdk/bin/jconsole.exe)
    • VisualVM:监控线程、内存情况(jdk/bin/jvisualvm.exe)

【内存泄漏】

  • java虚拟机栈StackOverflowError: 一般可能是递归
  • 方法区/元空间OutOfMemoryError:Metaspace:一般是动态加载的类太多
  • OutOfMemoryError:java heap space大对象未回收
    • -XX:+HeapDumpOnOutOfMemoryError //开启生成dump
    • -XX:HeapDumpPath=/home/app/dumps //配置dump地址
    • VisualVM导入

【CPU飙高】

  • 使用top命令查看cpu占用情况
  • 找到cpu高的进程id:40940
  • 找到进程所有的线程ps H -eo pid,tid,%cpu | grep 40940
  • 找到cpu高的线程id:40950
  • 线程id转为十六进制print “%x\n” 40950 //9ff4
  • jstack 40940查询堆栈信息
    在这里插入图片描述

多线程篇

在这里插入图片描述

线程基础

【线程和进程的区别】

  • 进程包含线程
  • 进程间内存独立,进程内线程间内存共享
  • 线程上下文切换快

【创建线程的方式】

  • 继承Thread类
    • 重写run() + new Thread().start()
  • 实现Runnable接口
    • 重写run() + new Thread(new Runnable()).start()
  • 实现Callable接口
    • 重写call() + new Thread(new FutureTask(new Callable())).start()
  • 线程池创建
    • Executors.newFixedThreadPool(3).submit(new Runnable())

  • 问:runnable和callable有什么区别?
  • run方法没有返回值,call方法有返回值FutureTask
  • call允许抛异常,run不允许抛异常

  • 问:run()和start()方法有什么区别?
  • run:线程将要执行的代码,可被当做普通方法多次调用
  • start启动线程,只能被调一次

【线程的状态】

在这里插入图片描述

  • 新建(new):new Thread();
  • 可运行(runnable):thread1.start();
  • 终止(terminated):线程获取到CPU执行权,轮询多次执行完毕
  • 阻塞(blocked):获取到CPU执行权,但是没获取到锁进入阻塞状态
  • 等待(waiting)thread1.wait();需要被notify()唤醒后切换到runnable状态
  • 计时等待(timed_waiting)thread1.sleep(50);进入计时等待状态,到时间后切换到runnable状态

【如何让线程按顺序执行】

  • join()阻塞写这行代码的线程,等待调用join的线程执行完后,再执行原线程,插队

【notify随机唤醒 / notifyAll唤醒所有】

【wait和sleep的区别】

  • 共同点: wait(),wait(50),sleep(50)效果都是让当前线程暂时放弃CPU使用权,进入阻塞
  • 不同点
    • 方法归属不同
      • sleep是Thread的静态方法
      • wait(),wait(50)都是object的成员方法
    • 醒来时机不同
      • sleep(50)和wait(50)都会在等待相应毫秒后醒来
      • wait可以被notify唤醒,不唤醒就一直等待
      • 他们都可以被打断唤醒
    • 锁特性不同
      • wait方法的调用必须要先获取wait对象的锁,sleep无此限制
      • wait方法执行后会释放锁
      • sleep如果在syncchronized代码块中执行,并不会释放对象锁

【如何停止正在运行的线程】

  • 使用退出标志,让线程执行完正常退出flag
  • 使用stop方法强行终止,(不推荐,已作废)
  • 使用interrupt方法中断线程
    • 打断阻塞的线程(sleep、wait、join)的线程,抛异常InterruptedException
    • 打断正常的线程,可根据打断状态标记是否退出线程

线程安全

【synchronized关键字的底层原理】

  • synchronized【对象锁】采用互斥的方式让同一时刻只能有一个线程持有锁
  • 底层是由Monitor实现,monitor是jvm级别的对象(C++实现),线程获得锁需要使用对象锁关联Monitor
  • Monitor包含3个属性
    • owner:关联获得锁的线程,只能有一个
    • entryList:关联处于阻塞状态的线程
    • waitset:关联处于waiting状态的线程

【synchronized关键字的底层原理-进阶】//TODO: 下次一定。。。

【JMM】java内存模型

  • JMM(Java Memory Model)java内存模型,定义了共享内存中,多线程程序读写操作的行为规范,保证指令的正确性
  • JMM把内存分为两块,一块是私有线程的工作内存,一块是所有线程的共享主内存
  • 线程之间相互隔离,线程跟线程交互需要通过主内存

【CAS】Compare And Swap(比较并交换)

  • 体现乐观锁的思想,在无锁状态下保证线程操作数据的原子性
  • CAS使用的地方很多:AQS框架、AtomicXXX类
  • 在操作共享变量的时候使用的自旋锁,效率跟高一些
  • CAS的底层是调用Unsafe方法,由操作系统提供,其他语言编写

  • 问:乐观锁和悲观锁的区别?
  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程修改共享变量,就算改了也没事,再重试
  • synchonized是基于悲观锁的思想,最悲观的估计,得防着其他线程来修改共享变量,我上锁你们别想改,我改完解开锁,你们再改

【Volatile关键字】

  • 保证线程间的可见性volatile修饰的共享变量,可告诉编译器(JIT)等别优化(vm参数-Xint可禁用即时编译器),让共享变量在线程间可见
  • 禁止指令重排序volatile修饰的共享变量,会在读、写共享变量时,加入不同屏障,阻止其他读写操作越过屏障,达到阻止重排序的效果,读开写尾

指令重排序:处理器为提高运行速度而做出违背代码原顺序的优化

【AQS】CAS修改state标识 + FIFO队列存线程

  • 是多线程中的队列同步器,是一种锁机制,是作为基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的
  • AQS内部维护了一个先进先出的队列,队列中存储排队的线程
  • AQS内部维护了一个属性state使用0/1标识是否持有锁
  • 在对state修改的时候使用的是CAS操作,保证多个线程修改的原子性

  • 问:AQS是公平锁还是非公平锁?
  • 答:都可以实现。
  • 公平锁:新线程进队列排队,从Head出队取锁
  • 非公平锁:新线程与Head抢锁

【ReentrantLock】CAS+AQS队列

  • ReentrantLock表示支持重新进入的锁,同线程内调用lock不会阻塞
  • 主要利用CAS+AQS队列实现
  • 支持公平锁和非公平锁,提供的构造器中无参默认非公平锁,带参公平锁

【Synchronized和Lock有什么区别?】

  • 语法层面
    • synchronized是关键字,源码在jvm中,用**C++**实现
    • Lock是接口,源码jdk提供,java实现
    • 使用synchronized时,退出代码块会自动释放锁Lock需手动unlock
  • 功能层面
    • 二者都属于悲观锁、都具备互斥、同步、锁重入功能
    • Lock提供了许多synchronized不具备的功能,例如公平锁、可打断、可超时、多条件变量
    • Lock有适合不同场景的实现,如ReentrantLock、ReentrantReadWriteLock(读写锁)
  • 性能层面
    • 没有竞争时,synchronized做了很多优化,如偏向锁,轻量级锁,性能还可
    • 竞争激烈时Lock的实现通常会提供更好的性能

【死锁产生的条件】

  • 产生的条件:一个线程同时获取多把锁互相有纠缠,容易发生死锁
  • 如何诊断
    • 当程序出现死锁时,我们可使用jdk自带的jps和jstack
    • jps:输出JVM的进程状态
    • jstack:查看进程内线程的堆栈信息,查看日志,检查是否有死锁,根据具体代码修复
    • 可视化工具jconsole、VisualVM也可以检查

【ConCurrentHashMap】

  • 底层数据结构
    • JDK1.7底层采用分段的数组+链表
    • JDK1.8底层数据结构跟HashMap1.8一样,数组+链表+红黑树
  • 加锁的方式
    • JDK1.7采用Segment分段锁。底层使用ReentrantLock
    • JDK1.8采用CAS添加新节点synchronized锁定链表或红黑树的首节点,相对Segment锁粒度更细,性能更好

【导致并发程序出现问题的根本原因】

  • 原子性:synchronized、lock
  • 内存可见性:volatile、synchronized、lock
  • 有序性:volatile

线程池

线程核心参数/执行原理】

  • corePoolSize核心线程的数量
  • maximumPoolSize最大线程数目 = 核心线程数 + 空闲线程数
  • keepAliveTime空闲线程释放倒计时
  • unit释放倒计时的时间单位,秒、毫秒等
  • workQueue当没有核心线程时新来的任务会加入到此队列排队,队列满时会创建空闲线程执行任务
  • threadFactory线程工厂,可以定制线程对象的创建,例如设置线程名字,是否是守护线程
  • handler拒绝策略,当所有线程都在忙,队列也满时,会触发拒绝策略
    • AbortPolicy:直接抛出异常默认策略
    • CallerRunsPolicy:用调用者所在的线程执行任务
    • DiscardOldestPolicy丢弃阻塞队列中最靠前的任务,并将当前任务放进去
    • DiscardPolicy直接丢弃任务
      在这里插入图片描述

【线程中常见的阻塞队列】

  • ArrayBlockingQueue:基于数组有界阻塞队列FIFO
    • 强制有界
    • 提前初始化数组
    • Node需提前创建好
    • 一把锁
  • LinkedBlockingQueue:基于链表有界阻塞队列FIFO
    • 默认无界,支持带参有界
    • 懒加载,创建节点时再添加数据
    • 入队生成新的Node
    • 两把锁(头尾)
  • DelayedWorkQueue:优先级队列,保证每次出队都是队列中执行时间最靠前
  • SynchronousQueue:不存储元素的阻塞队列,每个插入操作必等待一个移出操作

【如何确定核心线程数】

  • 高并发、时间短:N+1
  • 并发不高、时间长:
    • IO密集型任务:一般来说文件读写、DB读写、网络请求
      • 核心线程数大小设置为2N+1
    • CPU密集型任务:一般来说计算型代码、Bitmap转换、Gson转换
      • 核心线程数大小设置为N+1
  • 并发高,时间长:首先设计缓存、增加服务器、最后线程池设置
//查看机器CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());

【线程池的种类】

  • newFixedThreadPool定长线程池控制最大并发数,超出的会在队列中等待
  • newSingleThreadExecutor单线程线程池,只用唯一线程工作,保证所有线程顺序FIFO执行
  • newCachedThreadPool可缓存线程池,有任务时创建线程执行,无任务时回收
  • newScheduledThreadPool可延迟线程池,支持定时及周期性执行任务

【为什么不建议用Executors创建线程池】

在这里插入图片描述

使用场景

多线程场景

  • 公司解析及保存实时聊天数据
    • 使用SDK方式到企微批量拉取数据(实时1000/s,初始化可能上百万)
    • 拉取到数据需根据企微非对称加密密钥解析数据此时开启多线程解析
    • 解析完后发送kafka,消费端组装用户信息
    • 保存批量存ES+Cassandra
    • 文件类型分发文件Kafka消费端异步并发上传开启多线程(IO密集型))
  • 公司每晚定时任务修复校准所有的统计数据
    • 数据库/ES/Cassandra查询各公司统计元数据
    • 重新计算统计数据,并进行修复(开启多线程(CPU密集型))
    • 按公司CountDownLatch
  • 需要调用多个接口组装统计数据,各接口之间没有关系
    • 使用线程池,添加**不同接口任务,Future接收接口返回值(**格式规范)
    • future.get()可获取接口返回值,并可阻塞主线程,保证线程执行完后获取到返回值

【CountDownLatch】(为了等待线程池执行完再执行)

  • CountDownLatch(闭锁/倒计时锁):用来进行线程同步协作,等待所有线程递减完成,再执行后续操作
    • 构造函数初始化等待计数值
    • await()用来等待计数归零
    • countDown()用来计数减一
  • 场景:
    • ES数据批量导入
      • 计算总条数、设定每页条数、计算总页数设置为CountDownLatch(size)
      • 开启线程池
      • 分页查找,创建任务,提交到线程池执行
      • countDownLatch.await();主线程等待
      • 执行任务,每执行完一页,countDownLatch.countDown();计数减一
      • 计数归零,主线程继续执行,总计时/返回值
    • 统计数据按公司修复数据
      • CountDownLatch(总公司数)
      • 开启线程池
      • 公司修复数据countDownLatch.countDown();计数减一
      • countDownLatch.await();主线程等待
        • 等待计数归零,主线程继续执行,总计时/返回值

【@Async异步调用】(支持线程池)

  • 客服埋点异步推送提升接口响应时间

【如何控制某个方法允许的并发线程数】Semaphore

  • JUC包下提供一个工具类,底层AQS
  • 场景:对于资源有明确访问数量限制的场景,常用于限流
  • 使用方式
    • 创建Semaphore对象,设定容量
    • acquire()请求信号量,-1
    • release()释放信号量,+1

【ThreadLocal】(线程各用各的

  • ThreadLocal可实现线程间对[资源对象]的隔离避免线程安全问题
  • 线程内资源共享
  • 内部ThreadLocal有一个成员变量ThreadLocalMap存储资源对象
    • set方法,自己为key,资源为value进行赋值
    • get方法,自己为key,获取资源
    • remove移除当前线程关联的资源值
  • ThreadLocal内存泄露问题
    ThreadLocalMap中的key是弱引用,值为强引用,key会被GC释放,关联value内存不会释放,建议主动remove释放key、value

框架篇

Spring

【Spring中单例bean是线程安全的吗?】

  • 不是线程安全的,spring的bean通常无状态,某种程度上也可以说是线程安全。
  • 原型Bean:每次new新对象,线程之间不共享,不会有线程安全问题
  • 单例Bean:所有线程共享单例Bean,存在资源竞争
    • 如果bean是一个无状态bean(不会修改)则不存在线程安全问题
    • 如果bean是有状态的,则需要开发人员自己保证线程安全
      • 1.将bean的作用域改为**@Scope(value = “prototype”)**默认"singleton"
      • 2.尽量不要在@Controller/@Service等容器中定义静态变量(静态变量线程不安全)
      • 3.一定要定义变量的话,使用ThreadLocal来封装

【AOP】

  • 问:什么是AOP?
  • 答:面向切面编程,将无关业务却对多个对象产生影响的公共行为和逻辑,抽取公共模块服用,降低耦合
  • 问:你项目中用到的AOP
  • 答:记录操作日志切换公有云混合云数据库(有事务无法切库),spring实现的事务
  • 核心是:使用aop中的环绕通知+切点表达式(找到要记录日志的方法),通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),保存到数据库
  • 问:spring的事务是如何实现的?
  • 答:本质就是AOP功能,方法前开启事务,方法后提交或回滚

【spring中事务失效的场景】

  • 1.异常捕获处理

    • 原因:事务通知只有捕捉到目标抛出的异常,才会进行回滚,如果自己已经处理,则事务无法知悉
    • 解决方案:在catch代码块中添加**throw new RuntimeException(e)**抛出。
  • 2.抛出检查异常

    • 原因:spring默认只会回滚非检查异常,即运行时异常
    • 解决方案:配置**@Transactional(rollbackFor=Exception.class)**任何异常都回滚
  • 3.非public方法导致事务失效

    • 原因:spring为方法创建代理,添加事务通知,前提条件是该方式是public
    • 解决方法:改为public方法

【spring的bean的生活周期】

  • 通过BeanDefinition获取bean的定义信息
  • 调用构造函数实例化bean
  • bean的依赖注入
  • 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
  • Bean的后置处理器BeanPostProcessor-前置
  • 初始化方法(InitializingBean、init-method)
  • Bean的后置处理器BeanPostProcessor-后置
  • 销毁Bean

【spring的循环依赖问题】

  • 循环依赖:A依赖B,B依赖A
  • 循环依赖在Spring中是允许存在的,spring框架依据三级缓存可解大部分循环依赖(spring2.6后默认禁止)
    • 一级缓存(成品仓库):SingletonObjeces,单例池,存放初始化完成的bean对象
    • 二级缓存(半成品仓库):earlySingletonObjects,存放实例化但未初始化完的bean对象
    • 三级缓存(原材料仓库):singletonFactories,存放BeanFactory,可通过getObject()获取原始bean,(为解决有AOP行为的bean)
      在这里插入图片描述
  • 构造器注入循环依赖:由于构造器在依赖注入之前,spring处理不了,需手动处理
    • @Lazy懒加载

【springMVC的执行流程】

  • 视图阶段(JSP)
    • 用户请求发送到DispacyerServlet(前端控制器,总调度)
    • 总调度解析url去handlerMapping(处理器映射器)查询映射的类/方法等
    • 返回一个处理器链(并不是单独的类和方法,而是一系列前后关系的调用链,比如拦截器)
    • 总调度拿着调度链找HandlerAdapter(处理器适配器),
    • 适配器调用具体的处理器(Controller/Handler)并返回ModelAndView
    • 总调度将ModelAndView交给ViewReslover(视图解析器)渲解析后返回View对象
    • 总调度根据View进行渲染视图+数据填充
    • 总调度响应用户
      在这里插入图片描述
  • 前端后端分离
    • HandlerAdapter调用处理器后
    • 读取到方法上添加的**@ResponseBody**
    • 通过HttpMessageConverter将返回结果转换为JSON并响应
      在这里插入图片描述

【springBoot的自动配置原理】

  • SpringBoot的引导类上有个注解**@SpringBootApplication**,封装了
    • @SpringBootConfiguration:声明是一个配置类
    • @ComponentScan组件扫描,默认当前引导类所在包及其子包
    • @EnableAutoConfiguration:自动化注解的核心注解
      • 封装了**@Import**,配置了AutoConfigurationImportSelector(自动配置选择器)
      • 选择器读取项目路径META-INF/spring.factories文件,包含所有内置配置类
      • 选择器根据特定规则按需将配置类的所有Bean放入Spring容器中
      • 比如**@ConditionalOnClass**:判断是否有对应的class文件,有则加载

【spring常用注解】

  • Spring常用注解
    • @Component、@Controller、@Service、@Repository:使用在类上实例化Bean
    • @Autowried:使用在字段上根据类型依赖注入
    • @Qualifier:使用在字段上根据名称依赖注入,一般结合
    • @Scope:使用在类上,标注Bean的作用范围
    • @Configuration:使用在类上,标注此类为配置类,创建容器时会加载
    • @ComponentScan:使用在引导类上,标注扫描包范围
    • @Bean:使用在方法上,标注该方法的返回值存储到Spring容器中
    • @Import:使用Import导入的类会存储在Spring容器
    • @Aspect、@Before、@After、@Around、@Pointcut:用于切面编程(AOP)
  • SpringMVC常用注解
    • @RequestMapping:用于映射请求路径
    • @RequestBody:注解实现接收http请求的json数据,将json转为java对象
    • @RequestParam:指定请求参数的名称
    • @PathViriable:在请求路径中获取请求参数(user/{id}),传递给方法的形参
    • @ResponseBody:注解实现将controller方法返回对象转为json对象并响应给客户端
    • @RequestHeader:获取指定请求头数据
    • @RestController:@Controller+@ResponseBody
  • SpringBoot常用注解
    • @SpringBootConfiguration:组合了Configuration,标注配置类
    • @ComponentScan:Spring组件扫描
    • @EnableAutoConfiguration自动配置功能

MyBatis

【MyBatis执行流程】

  • 读取MyBatis-config.xml配置文件,加载运行环境和映射文件
    • SpringBoot中改为yml配置+注解
      • datasource: 数据库配置
      • mapper-locations: classpath:mapper/*.xml(扫描mapper文件路径)
      • type-aliases-package: com.xxx.pojo(简化resultType的包名)
      • configuration:map-underscore-to-camel-case: true(下划线转驼峰)
      • configuration:default-fetch-size: 100(默认查询批次数)
      • configuration:default-statement-timeout: 30(默认超时时间,单位s)
  • 构造会话工厂SqlSessionFactory
  • 会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
  • 操作数据库的接口,Executor执行器,同时负责查询缓存的维护
  • Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
  • 输入参数映射:java转数据库类型
  • 输出结果映射:数据库转java类型

【MyBatis的延迟加载问题】

  • MyBatis是否支持延迟加载
    • 支持,但默认是关闭的,lazyLoadingEnabled = false
    • 延迟加载的意思就是在一对一或一对多关联查询时,用到再查,不用不查
    • 局部可在<select fetchType = "lazy"/>配置单sql懒加载
  • Mybatis延迟加载的底层原理知道吗
    • 使用CGLIB创建目标对象的代理对象
    • 调用目标方法时,进入拦截器invoke方法,发现方法是null,执行sql查询
    • 获取数据后,调用set方法赋值,再继续查询方法,就有值了

【MyBatis的一级缓存和二级缓存】

  • 一级缓存:作用域SqlSession的本地HashMap,默认开启,当session进行flush或close后,会清空
  • 二级缓存:作用域namespace的本地HashMap,默认关闭
    • 开启方式:
      • 配置文件cacheEnable = true
      • XXXmapper.xml中添加 < cache/>
    • 注意事项
      • 二级缓存需要缓存的数据实现Serializable接口
      • 只有会话关闭或提交后,一级缓存数据才会转移到二级缓存
  • 当该作用域下数据库中数据进行了增、删、改后,默认将其作用域下所有select的缓存clear
  • 使用场景:耗时较高的统计分析sql,(单服务开启)

微服务篇

SpringCloud

【核心组件】

  • Eureka:注册中心
  • Nacos:注册中心/配置中心
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystrix:服务熔断
  • sentinel:服务保护
  • Zuul/Gateway:服务网关

【注册中心】

  • 服务注册:服务提供者将自己的服务名称、ip、端口等信息注册到eureka/nacos
  • 服务发现:消费者向eureka拉取服务信息列表,然后利用负载均衡算法选择一个发起调用
  • 服务监控:服务提供者每30s向eureka发送心跳,eureka服务90s没有检测到心跳,则从信息列表中剔除

【Eureka和Nacos的区别】

  • 共同点:
    • 都支持服务注册与发现
    • 都提供心跳健康检测
  • 区别:
    • Nacos支持服务主动检测健康:临时实例心跳模式非临时实例主动检测模式
      • 临时检测心跳不正常会剔除,非临时检测心跳不正常不会剔除
    • Nacos支持服务列表变更的主动推送,更新更及时
    • Nacos集群默认采用AP模式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP模式
    • Nacos还支持配置中心,Eureka只有注册中心,且已停更

【负载均衡】

  • 自带策略
    • RandomRule:随机选择
    • RoundRobinRule:轮询选择
    • WeightedResponseTimeRule:响应权重选择
    • ZoneAvoidanceRule(默认):区域敏感策略,按机房或机架分类然后轮询
  • 自定义策略
    • 实现IRule接口(全局)
    • 配置yml文件,配置某service的负载均衡策略(局部)

【服务降级/熔断】:降级太多就会熔断

  • 服务雪崩:一个服务失败导致之后链路服务都失败
  • 服务降级:为避免下游服务失败,服务兜底方案
    • 方案:在**@FeignClient中添加fallback**属性定义兜底实现类(降级)
  • 服务熔断:默认关闭,手动开启后,当检测到10s内请求失败率超过了50%,就触发熔断机制,之后每隔5s取尝试请求,不响应则继续熔断,响应则关闭熔断恢复正常请求。

【服务监控/部署】

  • Prometheus(普罗米修斯)
    • 监控服务健康、集群健康、CPU/内存占用
    • ES索引使用情况
    • Kafka队列消费堆积情况
    • 慢SQL、慢接口统计
    • 健康报警
    • 自定义监控规则
  • Docker容器化部署
  • Jenkins自动化构建工具
  • Kibana:ES监控、可视化

微服务业务

【服务限流】

  • 突发限流:为应对突发流量
  • 常规限流:为防止恶意攻击,系统保持正常运行,系统能够承受最大QPS
  • Nginx限流
    • 控制速率(突发流量):使用漏桶算法过滤,让请求以固定速率通处理
    • 控制并发:限制单个ip的连接数并发连接的总数
  • 网关限流
    • SpringCloudGateway中支持局部过滤器RequestRateLimiter来做限流,使用令牌桶算法
    • 可以根据ip或路径限流,可以设置每秒填充平均速率令牌桶总容量

【CAP原则+BASE理论】

  • CAP:一致性、可用性、分区容错性
    • 分布式系统节点通过网络连接,一定会出现分区问题§
    • 当分区出现时,系统的**一致性©可用性(A)**就无法同时满足
  • BASE理论
    • 基本可用
    • 软状态
    • 最终一致
  • 解决分布式事务的思想和模型
    • 最终一致思想:各分支事务分别提交,如果不一致,则补偿恢复数据(AP)
    • 强一致思想:各分支事务执行完业务不提交,等待彼此后统一提交或回滚(CP)

【分布式事务】

  • seata
    • seata的XA模式:CP模型,需要互相等待各分支事务提交,保证强一致性,性能差
    • seata的AT模式:AP模型,底层使用undo log实现,性能好
    • seata的TCC模式:AP,性能较好,不过需要人工编码实现
  • MQ模式:在同一事务内本地写数据+发送消息,异步,性能最好,AP模型,失败需手动补偿

【分布式服务的接口幂等性】

  • 幂等:多次调用方法或接口不会改变业务状态,重复调用和单次调用结果一致
  • 如果是新增数据,可以使用数据库的唯一索引
  • 如果是新增或修改数据
    • **分布式锁,**性能较低
    • 使用token+redis来实现,性能较好
      • 第一次请求,生成唯一token存入redis,返回给前端
      • 第二次请求,业务处理携带之前的token,redis验证,如果存在,则执行业务并删除token,如果不存在则返回不处理(高并发依然有漏洞,可setnx或redission)

【分布式任务调度】

  • xxl-job
    • 路由策略:xxl-job提供了很多的路由策略:轮询、故障转移、分片广播
    • 任务失败解决
      • 路由策略选择故障转移,使用健康的实例执行任务
      • 设置重试次数
      • 查看日志+邮件警告
    • 大量任务同时执行怎么解决
      • 部署集群让多个实例一起执行,路由策略分片广播
      • 在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行
  • 问:如何确保任务执行一次,不被多实例执行
  • 答:xxl-job单机策略或者分布式锁

消息中间件篇

RabbitMQ

【RabbitMQ如何保证消息不丢失】

  • 开启生产者确认机制(publisher confirm),确保生产者的消息能到达交换机
    • 失败后如何处理
      • 回调方法即时重发
      • 记录日志
      • 保存数据库,定时重发,成功即删
  • 开启持久化功能,确保消息在队列中不会丢失
    • 交换机持久化
    • 队列持久化
    • 消息持久化
  • 关闭消费者的确认机制为auto,改为手动确认消息处理成功后返回ack回执
  • 开启消费者失败重试机制,多次重试失败后将消息投递到异常交换机,交由人工处理

【RabbitMQ重复消费问题】

  • 原因:网络抖动 / 消费者挂了
  • 方案:
    • 每条消息设置唯一标识id
    • 幂等方案:分布式锁、数据库锁(悲观锁、乐观锁)

【RabbitMQ延迟队列/死信交换机?】

  • 场景:超时订单、限时优惠、定时发布
  • 实现:
    • 死信交换机 + TTL(消息存活时间)
      • 消息超时未消费、拒绝消费、队列满了都会进入死信队列
    • 延迟队列插件DelayExchange
      • 声明一个交换机,添加delayed属性为true
      • 发送消息时,添加x-delay头,值为超时时间

【RabbitMQ消费堆积如何解决?】

  • 增加消费者,提高消费速度
  • 消费者内开启线程池加快消息处理速度
  • 扩大队列容量,提高堆积上限,采用惰性队列
    • 在声明队列的时候设置属性x-queue-mode为lazy,即为惰性队列
    • 基于磁盘存储,消息上限高
    • 性能比较稳定,但基于磁盘存储,受限于磁盘IO时效性会降低

【RabbitMQ的高可用】

  • 镜像模式搭建的集群,一主多从,主节点操作,从节点同步
  • 主宕机后,镜像节点替代为新主(如果复制完成前宕机,则可能丢失数据)
    • 这种情况下可以采用仲裁队列,主从基于Raft协议,强一致。

【Kafka如何保证消息不丢失】

  • 生产者发送消息到Brocker丢失
    • 设置异步发送,发送失败使用回调进行记录或者重发
    • 失败重试,参数配置,可以设置重试次数
  • 消息在Brocker中存储丢失
    • 发送确认acks(0/1/all),选择all,确保所有副本保存成功
  • 消费者从Brocker接收消息丢失
    • 关闭自动提交偏移量,开启手动提交
    • 提交方式为同步+异步提交

【Kafka消息重复消费】

  • 关闭自动改为手动提交
  • 提交方式同步+异步
  • 唯一id
  • 幂等方案

【Kafka如何保证消费顺序】

  • 问题原因:一个topic数据可能存在不同的分区内,每个分区按顺序存储的偏移量,如果消费者关联了多个分区则不能保证顺序性
  • 解决方案:
    • 发送消息指定分区
    • 发送消息按照业务设置相同的key

【Kafka高可用方案】

  • 集群模式:一个集群由多个Broker实例组成,一个宕机,其他继续服务
  • 分区备份机制(主从复制):同步保存副本(ISR)+普通异步副本,鸡蛋放在不同的篮子

【Kafka清理机制】

  • kafka存储结构
    • kafka种topic的数据存在分区上,当分区文件过大时会分段存储segment
    • 每个分段都在磁盘上以index+log形式存储
    • 分段的好处是:
      • 减少单文件大小,查找方便
      • 方便kafka清理日志
  • 日志清理策略
    • 根据消息保留时间,超过168小时(默认7天)就会触发清理
    • 根据大小,大于一定阈值删除最久消息(默认关闭)

【Kafka的高性能设计】

  • 消息分区:不受单台服务器的限制
  • 顺序读写:磁盘顺序读写,提升读写效率
  • 页缓存:把磁盘中的数据缓存到内存中,磁盘访问变为内存访问
  • 零拷贝减少上下文切换及数据拷贝
    • 我从仓库拿出来+发快递 ===》》我从仓库发快递
  • 消息压缩:减少磁盘IO和网络IO
  • 分批发送:将消息打包批量发送,减少网络开销

数据库篇

MySql

【如何定位慢查询】

  • 表象页面加载过慢接口压测响应时间长普罗米修斯监控告警
  • 如何定位
    • 开源工具
      • 调试工具:Arthas
      • 运维工具:Prometheus、Skywaliking
    • Mysql慢日志(/etc/my.cnf)配置
      • slow_query_log = 1 #开启
      • long_query_time = 2 # 慢sql阈值2s

【如何分析慢查询】

  • 慢查询一般由
    • 聚合查询:新增临时表
    • 多表查询:优化sql语句
    • 表数据量过大查询:添加索引
    • 深度分页查询
  • SQL执行计划 explan / desc
    • possible_key可能使用到的索引,可能是多个
    • key:当前sql实际命中的索引
    • ken_len:当前命中索引占用的大小
    • Extra重要的额外信息
      • using filesort(重要)
      • using temporary(重要)
      • using index(重要)
      • using index condition:直接使用了索引,但是需要回表查询数据
      • using where
      • using join buffer
      • impossible where
      • select tables optimized away
      • distinct
    • type
      • Null:未使用到表
      • system:查询系统表
      • const:主键查询
      • eq_ref:主键查询/唯一索引查询
      • ref:索引查询
      • range:范围查询
      • index:索引树查询
      • all:全盘扫描

【索引】

  • 什么是索引:查询快的目录
    • 索引(index)是帮助mysql高效获取数据的一种数据结构(有序)
    • 提高数据检索效率降低数据库IO成本(不需要全表扫描)
    • 通过索引列对数据进行排序,降低数据排序成本降低CPU消耗
  • 索引的底层数据结构InnoDB引擎采用B+树
    • 阶数更多,路径更短
    • 磁盘读写代价B+树更低非叶子节点只存储指针,叶子节点存储数据
    • B+树便于扫库和区间查询,叶子节点是一个双向链表

【聚簇索引?非聚簇索引】

  • 聚簇索引(聚集索引):B+树的叶子节点保存了整行数据row()有且只有一个
    • 有主键选择主键
    • 没有主键,使用第一个唯一索引(Unique)
    • 没有主键,没有唯一键,自动生成一个rowid作为隐藏的聚簇索引
  • 非聚簇索引(二级索引):B+树的叶子节点保存了对应的主键id,可以有多个

  • 问:什么是回表查询?
  • 答:通过二级索引查到对应的主键id,再去主键索引查询整行数据

【覆盖索引】不需要回表

  • 查询使用了索引,并能在索引中找到所需的所有字段。

  • 问:MySql超大分页怎么处理?
  • 答:超大索引一般是数据量比较大时,limit分页查询,需要对数据进行排序,效率很低,我们可以用覆盖索引+子查询解决

【索引创建的原则】

  • 数据量较大,且查询比较频繁的表
  • 常作为**查询条件(where)、排序(order by)、分组(group by)**操作的字段
  • 字段内容区分度高
  • 内容较长的字符串可以使用前缀索引
  • 尽量使用联合索引
  • 要控制索引的数量,越多维护索引的代价越大,会影响增删改的效率
  • 如果索引不能存Null,在建表时添加not null优化器可以更好的确定有效索引

【索引失效】

  • 违反最左前缀法则
  • 范围查询后面的列,索引失效
  • 不要在索引列上进行运算操作,索引失效
  • 字符串不加单引号,类型转换造成索引失效
  • 以**%开头的Like模糊查询,索引失效**

【SQL优化】

  • 表的设计优化
    • 根据实际情况选择合适的数值类型
    • 合适的字符串类型,char定长效率高,varchar可变长度
  • 索引的创建及优化
  • sql语句优化
    • 避免索引失效
    • 避免select * 回表
    • 尽量union all代替union(多一次过滤)
    • join优化,能用innerjoin就不用left join/right join小表驱动大表
  • 主从复制读写分离,不让数据写影响读操作
  • 分库分表

【事务】

  • 事务是一组操作,统一提交或撤销要么同时成功,要么同时失败
  • 特性
    • 原子性(Atomicity)不可分割的最小操作单元,要么一起成功,要么一起失败
    • 一致性(Consistency):事务完成时,所有数据一致
    • 隔离性(Isolation):数据库提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
    • 持久性(Durability):事务一旦提交或回滚,对数据库的数据改变是永久的

【并发事务问题?MySQL的隔离级别】

  • 并发事务的问题
    • 脏读:一个事务读到另一个事务还未提交的数据
    • 不可重复读:一个事务先后读取同一条记录不一样
    • 幻读:一个事务按条件查不到数据,插入数据时,发现已经有了,好像出现"幻影"
  • 隔离级别
    Read Uncommitted 未提交读:有脏读、不可重复读、幻读的问题
    Read Committed 已提交读:有不可重复读、幻读的问题
    Repeatable Read 可重复读(默认):有幻读问题
    Serializable 串行化:没问题,性能差

【undo log和redo log】

  • redo log:记录的是数据页的物理变化,服务宕机时用来同步数据
  • undo log:记录逻辑日志,当事务回滚时,通过逆操作恢复原来的数据
  • redo log保证了事务的持久性,undo log保证了事务的原子性和一致性

【MVCC】//TODO

【主从同步】binlog

  • 主库在事务提交时,会把数据变更记录在二进制日志文件Binlog中,DDL+DML
  • 从库开线程IOThread****读取主库的binlog,写入从库的中继日志Relay Log
  • 从库再开线程SQLThread执行relay log

【分库分表】

  • 业务介绍:简历上的项目,数据量级(1000W或超过20G)
  • 分库分表的时机
    • 项目业务数据逐渐增多,或业务发展比较迅速
    • 优化已经解决不了性能问题(主从读写分离、查询索引
    • IO瓶颈(磁盘IO、网络IO)
    • CPU瓶颈(聚合查询、连接数太多)
  • 具体拆分策略
    • 垂直分库:根据业务拆分,高并发下提高磁盘IO和网络连接数
      • 根据功能分为业务库、统计库、管理库
    • 垂直分表冷热字段数据分离,多表互不影响
      • 客户字段50-100个,拆分为不同维度的表
    • 水平分库:将一个库的表拆分为多个库,解决海量数据存储和高并发的问题
      • 公有云、私有云、混合云数据库拆分
    • 水平分表:解决单表存储和性能的问题
      • 按年拆分,按公司私有化

企业场景篇

设计模式

【简单工厂模式】(解耦new方法,但有违开闭原则5

属编程习惯,非设计模式

  • 抽象产品:定义产品规范,抽象类/接口
  • 具体产品实现或继承抽象产品的子类
  • 具体工厂:提供创建产品的静态方法

在这里插入图片描述

【工厂方法模式】(剥离(工厂+产品),满足开闭原则

  • 抽象产品:定义产品规范,抽象类/接口
  • 具体产品实现或继承抽象产品的子类(由具体工厂创建,一一对应)
  • 抽象工厂:提供创建产品的接口
  • 具体工厂实现抽象工厂,完成具体产品的创建
  • 优缺点:类太多,但有新产品时只需要添加新的工厂+新的产品类即可,无需修改原代码,可插拔满足开闭原则
//工厂接口
@SPI("dubbo")
public interface RegistryFactory {

    /**
     * 连接注册中心.
     * 
     * 连接注册中心需处理契约:<br>
     * 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。<br>
     * 2. 支持URL上的username:password权限认证。<br>
     * 3. 支持backup=10.20.153.10备选注册中心集群地址。<br>
     * 4. 支持file=registry.cache本地磁盘文件缓存。<br>
     * 5. 支持timeout=1000请求超时设置。<br>
     * 6. 支持session=60000会话超时或过期设置。<br>
     * 
     * @param url 注册中心地址,不允许为空
     * @return 注册中心引用,总不返回空
     */
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);
}
//抽象工厂
public abstract class AbstractRegistryFactory implements RegistryFactory {
	public Registry getRegistry(URL url) {
    	url = url.setPath(RegistryService.class.getName())
    			.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
    			.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
    	String key = url.toServiceString();
        // 锁定注册中心获取过程,保证注册中心单一实例
        LOCK.lock();
        try {
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            //具体注册中心调到的方法
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // 释放锁
            LOCK.unlock();
        }
    }
	//具体工厂所需要实现的类
    protected abstract Registry createRegistry(URL url);
}

//以zookeeper为例
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
	
	private ZookeeperTransporter zookeeperTransporter;

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
		this.zookeeperTransporter = zookeeperTransporter;
	}

	public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

}

【抽象工厂模式】(升级版工厂方法)

  • 抽象产品:定义产品规范,抽象类/接口(同上)
  • 具体产品:实现或继承抽象产品的子类(由具体工厂创建,一一对应)(同上)
  • 抽象工厂:提供创建多种产品的接口
  • 具体工厂:实现抽象工厂,完成具体产品的创建(同上)

【策略模式】

  • 定义:通过对算法进行封装,将算法的实现和使用分开,并委派给不同的对象管理算法
  • 角色
    • **抽象策略(Strategy)**类:给出所有具体策略类的接口,接口/抽象类
    • 具体策略(Concrete Strategy)类:实现了抽象策略的类,提供具体的算法实现
    • 环境(Context)类:持有一个策略类的引用,供客户端调用
  • 场景同一行为的不同形式(算法)
    • 订单支付策略(支付宝、微信、银行卡)
    • 解析不同类型的excel(xls格式、xlsx格式)
    • 不同打折促销算法(满300元9折、满500元8折、满1000元7折)
      物流运费阶梯计价(5kg以下、5-10kg、10-20kg、20kg以上)
    • Feign负载均衡策略:轮询、随机、自定义、、、

【责任链模式】拦截器、过滤器

  • 定义: 为避免请求处理的耦合,链上各负责模块各司其职是自己的就处理,否则交给别人,直到处理完
  • 角色:
    抽象处理者(Handler):定义一个处理请求的接口,包含处理方法下一个负责人
    具体处理者(Concrete Handler)实现抽象处理者的处理方法,判断能干就干,不能干交给别人
    客户类(Client)创建处理链,制定顺序并启动
  • 优点:
    • 降低了请求发送者和处理者的耦合度
    • 增强了系统的可扩展性(新增流程)
    • 增强了给对象指派职责的灵活性
    • 简化对象之间连接
    • 责任分担(满足单一职责原则6)
  • 场景
    • 内容审核:视频、文章、课程
    • 订单创建:校验参数、填充订单、算价、落库、返佣
    • 简易的流程审批:组长审批、主管审批、副总裁、总裁

技术场景

【单点登录SSO】

  • 定义:单点登录(Single Sign On),简称SSO,只需要登录一次,就可以访问所有信任的应用系统
  • 旧的登录:单体tomcat。session可共享。没问题
  • 分布式/微服务登录:JWT、Oauth2、CAS
  • JWT:
    • 用户访问系统,网关判定token是否有效
    • 无效则返回401认证失败并跳转到登录页
    • 用户发送登录请求,返回浏览器JWTToken,浏览器保存到cookie
    • 再访问时,带着token,网关验证后路由到目标服务器
      在这里插入图片描述

【权限认证】

  • RBAC模型(Role-Based Access Control):基于角色的控制访问
    • 3个基础部分组成:用户、角色、权限
    • 具体实现
      • 5张表:用户表、角色表、权限表、用户角色中间表、角色权限中间表
      • 7张表:用户表、角色表、权限表、用户角色中间表、角色权限中间表、菜单表、权限菜单中间表
        -权限框架:Apache shiro、SpringSecurity

【上传数据/网络传输的安全性】

  • 答:给前端一个秘钥,将数据加密后传到后台,后台解密处理

  • 对称加密(AES):文件加解密使用相同的秘钥

    • 优点:加密速度快,效率高
    • 缺点:相对不太安全(不要保存敏感信息)
  • 非对称加密:公钥加密,私钥解密

    • 优点安全性较高
    • 缺点:加密解密速度慢,建议少量数据

【项目难题如何解决】

  • 设计模式(工厂、策略、责任链)
  • 线上BUG(CPU彪高、内存泄露、线程死锁)
  • 调优(慢接口、慢SQL、缓存方案)
  • 组件封装(分布式锁、接口幂等、分布式事务、支付通用)
  • 什么背景(技术问题)
  • 过程(解决问题的过程)
  • 最终落地方案

【项目日志怎么采集的】

  • 为什么:日志是定位系统问题的重要手段
  • 方式:
    • 常规采集:按天保存日志
    • ELK:即Elasticsearch、Logstash、Kibana
      • ElasticSearch:全文搜索分析引擎,可对数据进行存储。搜索、分析
      • Logstash是一个数据收集引擎,动态收集数据并存储
      • Kibana是一个数据分析和可视化平台,配合es检索分析

【常见日志】

  • 监控日志变化:tail -n 100 -f xx.log(最后100行)
  • 按行号查询
    • 尾部100行:tail -n 100 xx.log
    • 头部100行:head -n 100 xx.log
    • 行号区间:cat -n xx.log|tail -n +100 | head -n 100(查询100-200行)
  • 按关键字查找
    • cat xx.log |greo “debug”
  • 按日期查询
    • sed -n ‘/2023-10-18 00:00:00/,/2023-10-19 00:00:00/p’ xx.log
  • 日志太多
    分页查找:cat -n xx.log | grep “debug” | more回车翻页
    过滤到文件:cat -n xx.log |grep “debug” > debug.txt

【生产问题怎么排查】

  • 先分析日志
  • 远程debug(一般不允许)
    • 首先保证远程和本地代码一致
    • 远程启动加支持远程的参数
    • idea=》Edit Configurations=> Remote JVM debug
    • 设置远程ip+端口+配置,然后点debug按钮
    • 开始断点调试

【如何快速定位系统的瓶颈】

  • 压测(性能测试)
    • 压测目的:给出系统当前的性能状况,定位性能瓶颈或潜在瓶颈
    • 指标:响应时间、QPS、并发数、吞吐量、CPU利用率、内存使用率、磁盘IO、错误率
    • 压测工具:LoadRunner+Apache Jmeter
    • 后端:接口慢、代码报错、并发
  • 监控、链路追踪
    • 监控:Prometheus+Grafana
    • 链路追踪:skywalking、Zipkin
  • 线上诊断工具Arthas(阿尔萨斯)
    • 官网:https://arthas.aliyun.com/
      在这里插入图片描述

  1. 学习资源参考新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题) ↩︎

  2. LRU:最近最少使用,近期不热就被淘汰
    LFU:最少频率使用,热度不高就被淘汰 ↩︎ ↩︎

  3. CAP原则:在一个分布式系统中,一致性、可用性、分区容错性这三个要素最多同时满足两点,不可能三者兼顾
    一致性(Consistency):分布式系统所有数据备份在同一时刻是否一致
    可用性(Availability):保证每个请求不管成功失败都有相应
    分区容错性(Partition tolerancec):系统中任意信息的丢失或失败不会影响系统的继续运作 ↩︎

  4. STW(Stop-The-World):暂停所有线程,等待垃圾回收 ↩︎

  5. 开闭原则:开发中的对象(类、模块,函数)应该支持扩展,封闭对原文件的修改 ↩︎

  6. 单一职责原则:一个对象应该只包含单一的职责,并被完整的封装在一个类中 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值