面试题汇总
http://t.csdnimg.cn/jtuir
– Bryan
Redis
Redis 使用场景
缓存:穿透,击穿,雪崩,双写一致,持久化,数据过期,淘汰策略
分布式锁:setnx/redisson
缓存穿透
不存在的数据,反复去数据库中查询
解决方案
- 存空值
- 布隆过滤器:通过算法计算hash,不一定能判断出存在,但是一定可以判断不存在
缓存击穿
设置过期时间,刚失效有大量数据同时请求
解决方案:
- 互斥锁 强一致性
- 逻辑过期,新开线程更新数据 高可用性
缓存雪崩
同一时段内,大量key同时失效
解决方案:
- 给不同key的ttl加上随机值
- 利用Redis集群提高服务的可用性
- 给缓存业务限流降级
- 给业务加上多级缓存
双写一致
保证redis和mysql的数据一致性
读操作:缓存命中,直接返回;缓存未命中,查询数据,写入缓存,设定超时时间
写操作:延迟双删 删除缓存-修改数据- 延时 sleep(100) -删除缓存
双写一致:异步通知保证数据一致
回答:redisson的读写锁方案
持久性
RDB 和AOF
-
RDB redis database file redis数据快照文件
将内存中所有文件保存到磁盘中
自动配置 reids.conf
- AOF append only file 追加文件
默认关闭
记录命令
数据过期策略
- 惰性删除 不会删除,访问时如果过期就删除 对cpu友好对内存不友好
- 定期删除
数据淘汰策略
默认 - 不淘汰任何key 但不允许在新写入
- LRU (least recently used) 最近最少使用 当前时间减去最后一次访问时间 越大跃先淘汰
- LFU(least frequently used) 最少频率使用
分布式锁使用场景
集群情况下的定时任务,抢单,幂等
setnx set if not exists
命令:set lock value NX EX 10 NX 互斥 EX 过期时间
释放:DEL key
- watch dog 续期 releaseTime/3
- 可重入锁(同一线程中)
- 主从一致性
主从复制,主从同步
主从复制 给一个版本号,bgsave生成RDB文件,更新成功 版本号更新
offset 偏移量 版本号 replid数据集标记 master上的唯一值
增量同步 根据offset 同步offset之后的数据
哨兵模式
监控,自动故障恢复,通知
脑裂问题 : 重选master后 旧master恢复了 强制降级为slave 导致数据丢失
解决方案:设置从节点数量,增大同步延时时间
分片
面试题
- redis 单线程快的原因
存内存操作;单线程避免上下文,锁和安全等;使用I/O多路复用模型,非阻塞I/O
- 解释一下I/O多路复用模型
单个线程,同时监听多个socket
mysql
如何定位慢查询
运维工具Skywalking 报告展示哪个sql慢
sql语句慢 怎么分析
聚合查询 - 新增临时表
多表查询 - 优化sql语句
表数据量过大查询 - 添加索引
执行计划 sql 前加Expain/DESC 分析
- key:用到的索引
- key_len 索引所占空间 Extra- 额外优化建议
- type-null/system/const/eq_ref/ref/range/index(索引树扫描)/all
深度分页查询
聚簇索引
聚簇索引:数据与索引放到一块,B+树叶子节点保存了整行数据,有且只有一个
非聚簇索引:数据与索引分开存储,B+树的叶子节点保存主键,可以有多个
回表 通过二级索引找多对应主键值,到聚集索引中查找整行数据的动作
**覆盖索引 **查询使用了索引,并且返回的值能在改索引中全部找到
Mysql超大分页怎么处理 覆盖索引+子查询
索引创建原则
主键索引/唯一索引/联合索引
- 针对数据量比较大,查询频繁的 10w+
- 针对常查寻的字段 where ,order by ,group by
- 选择区分度高的字段
- 字符串类型 尽量使用前缀索引
- 尽量使用联合索引,减少回表
- 控制索引数量,索引越多,成本越高
- 如果索引列不包含null ,在创建时约束字段 not null
索引失效
- 违反最左前缀法则
- 范围查询 右边的列,不能使用索引
- 索引上运算操作
- 字符串不加单引号
- 以%在前的模糊查询
SQL优化经验
- 表设计优化
根据《阿里开发手册》嵩山版;sql优化 避免 select *
- 索引优化
- sql语句优化
- 主从复制,读写分离
- 分库分表
事务的特性
事务相关
- A(原子性)C(一致性)I(隔离性)D(持久性)
事务隔离级别
并发事务问题:脏读,不可重复读,幻读
隔离级别:读未提交,读已提交,可重复读(默认),串行化
undo log和redo log区别
缓冲池 主内存的一个区域 在执行增删改查先操作缓冲池 按一定频率同步磁盘 减少IO次数
数据页 inndb数据页的最小单元 16k
redo log 重做日志,记录事务提交时的物理修改 保证事务持久性
undo log 回滚日志,记录修改前的信息。主要作用 提供回滚和MVCC(多版本并发控制) 保证一致性和原子性
保证隔离性:锁 / MVCC
mvcc 多版本并发控制 保证读写没有冲突
mysql主从同步原理
二进制日志,主库将DDL和DML写到 binlog中 - 从库读取-写入从库Relay log-从库重做中继日志中的事件
分表分库
垂直分库 微服务 高并发下 减少IO 提高效率
框架篇
Spring
Spring框架中单例bean时线程安全的吗?
不是
什么时AOP,在项目中有没有用过AOP?
面向切面编程,找到切点,在切点操作,减少重复代码降低耦合度
-
记录操作日志
-
缓存处理
-
spring 事务处理
在方法之前拦截 开启事务 结束后提交事务 如果报错 回滚事务
适用于大批量插入操作
Spring中事务失效的场景有哪些?
- 异常捕获处理
- 抛出检查异常
- 非public方法
spring bean的生命周期
BeanDefinition 对象-构造函数-依赖注入-Aware接口-BeanPostProcessor#Before-初始化方法-BeanPostProcessor#after-销毁
Spring的循环引用问题
三级缓存解决循环依赖问题
-
一级缓存 单例池 singleton 对象
-
二级缓存 缓存早期的bean对象 半成品对象 一二级解决一般对象的循环依赖
-
三级缓存 缓存ObjectFactory,表示对象工厂,用来创建某个对象的 singletonFactories对象 一二三级解决代理对象循环依赖问题
question 2
A依赖B,B依赖A,用的是构造方法注入
A:bean周期中构造方法时第一个执行,spring框架不能解决依赖注入
使用@Lazy进行懒加载 什么时候需要对象再进行创建
SpringMVC的执行流程
Springboot的自动配置原理
@SpringbootAplication
- SpringbootConfiguartion
- EnableAutoConfiguration
- ComponentScan
其中@EnableAutoConfiguartion是实现自动化配置的核心注解。该注解通过@Import注解导入对应的配置选择器
Spring框架中常见的注解
微服务
Eureka 注册中心
Eureka和Nacos的区别
模块 | Nacos | Eureka | 说明 |
---|---|---|---|
注册中心 | 是 | 是 | 服务治理基本功能,负责服务中心化注册 |
配置中心 | 是 | 否 | Eureka需要配合Config实现配置中心,且不提供管理界面 |
动态刷新 | 是 | 否 | Eureka需要配合MQ实现配置动态刷新,Nacos采用Netty保持TCP长连接实时推送 |
可用区AZ | 是 | 是 | 对服务集群划分不同区域,实现区域隔离,并提供容灾自动切换 |
分组 | 是 | 否 | Nacos可用根据业务和环境进行分组管理 |
元数据 | 是 | 是 | 提供服务标签数据,例如环境或服务标识 |
权重 | 是 | 否 | Nacos默认提供权重设置功能,调整承载流量压力 |
健康检查 | 是 | 是 | Nacos支持由客户端或服务端发起的健康检查,Eureka是由客户端发起心跳 |
负载均衡 | 是 | 是 | 均提供负责均衡策略,Eureka采用Ribion |
管理界面 | 是 | 否 | Nacos支持对服务在线管理,Eureka只是预览服务状态 |
基本参数
server.port=7001
spring.application.name=eureka-server
eureka.client.serviceUrl.defaultZone=http://localhost:7002/eureka/
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true
#禁用 Eureka 的 ReadOnlyMap 缓存 (Eureka 端)
eureka.server.use-read-only-response-cache=false
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.prefer-file-system-access=false
#客户端信息上报到服务的周期
eureka.client.instance-info-replication-interval-seconds=5
#客户端拉取服务器配置信息的周期,默认30s
eureka.client.registry-fetch-interval-seconds=5
#启用主动失效,并且每次主动失效检测间隔为 3s (Eureka 端)
eureka.server.eviction-interval-timer-in-ms=3000
#服务过期时间 (服务提供方)
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次 (服务提供方)
eureka.instance.lease-renewal-interval-in-seconds=5
#ribbon 刷新时间 (客户端)
ribbon.ServerListRefreshInterval=5000
feign+ribbon+hystirx交互概览
Feign 接口调用
- 作为HTTP的客户端替代RestTemplate,支持注解的方式
- Feign组件中引入了Ribbon和Hystrix组件,这使得Feign也能够为我们提供负载均衡、熔断、降级的功能;
在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。
ribbon 负载均衡
- 服务雪崩 一个服务失败 导致整个服务失败
- 服务降级 一种保护机制 根据f
- 服务熔断 服务不可用 每隔5s进行尝试请求
Hystrix熔断
在微服务系统中,Hystrix 能够帮助我们实现以下目标:
- 保护线程资源:防止单个服务的故障耗尽系统中的所有线程资源。
- 快速失败机制:当某个服务发生了故障,不让服务调用方一直等待,而是直接返回请求失败。
- 提供降级(FallBack)方案:在请求失败后,提供一个设计好的降级方案,通常是一个兜底方法,当请求失败后即调用该方法。
- 防止故障扩散:使用熔断机制,防止故障扩散到其他服务。
- 监控功能:提供熔断器故障监控组件 Hystrix Dashboard,随时监控熔断器的状态。
SkyWalking 服务监控 追踪链路
监控服务请求时间 找到耗时最多的服务
微服务限流
原因 并发量大 / 防止恶意请求
- 漏桶算法
- 令牌算法
CAP理论和BASE理论
Consistency 一致性
Availability 可用性
Partition 分区容错性
base 对CAP的解决思路
- basically available 基本可行性
- soft state 软状态
- Eventually consistency 最终一致性
分布式事务解决方案
Seata架构
tc – 事务协调器 维护全局和分支事务的状态 协调全局事务的提交和回滚
tm – 事务管理器 全局事务范围 开始全局事务的执行 提交 回滚
rm – 资源管理器 每个微服务
-
XA
CP模型 需要等待各个事务分别提交 银行业务
-
AT
AP模型 底层使用undolog实现 性能高 互联网
-
TCC
AP 需要人工编码实现
MQ 架构
在A执行写事务时 在同一事物中,发消息给B服务,异步性能好
分布式幂等问题
-
数据库唯一索引
-
token+redis
第一次请求:获取token - 存入redis (k:user v:token) - 返回token
第二次请求:带着token请求服务端,查询是否存在token,存在执行业务流程,不存在则直接返回 - 返回请求结果
-
分布式锁
快速失败 控制锁的粒度
分布式任务调度 xxl-job
- 解决集群任务执行问题
- cron定义灵活
- 定时任务失败了,重试和统计
- 任务量大,分片执行
-
xxl-job 任务有哪些
轮询,故障转移分片广播
-
失败了怎么办
故障转移+失败重试+日志分析 --》邮件告警
-
大数量执行
消息中间件
RabbitMQ
保证消息不丢失
发送MQ丢失
消息成功后,有回值 ack
数据丢失处理方案
- 回调方法即时重发
- 记录日志
- 保存数据库 定时重发 ,成功后删除数据
MQ内丢失
交换机和队列做持久化
消费者数据丢失
当消费者处理成功后,返回ack回值
设置重试次数,加入死信队列 或人工处理
MQ重复消费问题
唯一id
分布式锁
死信交换机
延时队列=死信交换机+TTL
死信交换机:消费者拒绝或处理消息失败 消息过期 投递消息堆积满了,最早的消息成为死信
TTL: time to live 消息存活时间 队列设置了存活时间 消息本身设置了存货时间
消息堆积
- 增加消费者
- 在消费者开启线程池,处理更多的消息
- 扩大队列容量,提高堆积上限
惰性队列:将消息存在磁盘中,只有在加载时才会去读取,支持百万存储
高可用
普通集群 镜像集群 仲裁集群
kafka
消息丢失
异步发送,重试
brocker中,存储丢失,设置acks模式为all
消费者,关闭自动提交偏移量,手动提交,同步加异步(finally)
重复消费
手动提交,幂等性
前端人脸识别+滑块功能
如何保证消息的顺序性
指定分区 分区内时有序的
保证kafka的高可用
消息分区 不受单台服务器限制,可以不受限制的处理更多数据
顺序读写 磁盘顺序读写日高读写效率
页缓存 读写磁盘数据中,将数据存到页缓存中
零拷贝 减少上下文切换及数据拷贝
消息压缩 减少此怕IO 网络IO
分批发送 将消息打包批量发送 减少网络开销
集合
时间复杂度
算法的时间和数据规模之间的增长关系
表示量级 O(n) O(n^2)
速记:常对幂指阶
空间复杂度: 算法暂用的额外存储空间和数据规模之间的增长关系
List
数组
是一种连续的内存空间存储相同数据类型数据的线性数据结构
寻址公式:a[i]=baseAddress+i*dataTypeSize
- 为什么所引从0开始?
根据寻址公式,对于CPU来说 会多一次 i-1 的操作 有性能损耗
-
操作数据的时间复杂度
-
查询
已知索引:O(1)
未知索引:O(n) 若排序,使用二分查找法 O(log n )
-
插入/删除
O(n)
-
ArrayList源码分析
ArrayList的底层实现原理时什么?
- ArrayList的底层是用动态数组实现的
- 初始容量为0
- 1.5倍扩容
- 添加数组时
- 确保已使用数组长度+1后足够下一个数据
- 计算容量,如果不符合1,则1.5倍扩容
- 扩容后,将添加的数组加到size的位置上
- 返回成功的布尔值
new ArrayList(10) 扩容了几次
声明和实例了一个Arraylist 制定了容量为10 没有扩容
如何实现数组和List之间的转换
String[] str={"a","b"};
List<String> list = Arrays.asList(str); Arraylist指向了数据的引用(同一个地址),如果数组修改,ArrayList也会受到影响
String[] array = list.toArray(new String[list.size()]); 数组拷贝
ArrayList和LinkedList的区别
单向链表
- 时间复杂度
查询 头节点 O(1)
查询其他节点O(n)
插入删除 头 O(1) 其他O(n)
双向链表
前驱节点+data+后继节点
支持双向遍历
-
时间复杂度
查询:头尾 O(1) 其他O(n)
插入删除 头尾 O(1) 其他O(n)
区别
-
底层数据结构
ArrayList 动态数据
LinkedList 双向链表
-
操作效率
查询:ArrayList 查询 内存连续,根据寻址公式 LinkedList不支持下标
-
空间占用
Arraylist 内存连续,相对节省空间
LinkedList 双向链表,需要保存两个指针,相对占内存
-
线程安全
均不安全
线程安全方案工具类
- Collections.synchronizedList(new ArrayList())
HashMap
二叉树
每个节点有两个’叉‘ ,分别为左子节点和右子节点。
- 二叉搜索树
左节点小于父节点,右节点大于父节点 没有相等的值 时间复杂度 O(log n)
红黑树
自平衡二叉树 BST 时间复杂度 O(log n)
性质:
- 节点要么时红色,要么是黑色
- 根节点是黑色
- 叶子节点是黑色
- 红色节点的子节点必须是黑色
- 从任意节点到叶子节点的所有路径都包含相同数目的黑色节点
目的是为了一旦不符合这些规则就会发生旋转,保证数据的平衡
散列表
hash表,根据k直接访问内存位置V的数据结构,由数组演化来。
hash冲突:散列冲突-链表法
DDos攻击
分布式拒绝服务攻击 效率问题
hashmap实现原理
底层用hash表数据结果+链表+红黑树
添加时,hash(key)相同,key也相同,则替换
不同存入红黑树或者链表(链表长度大于8,数组长度大于64,则链表->红黑树)
获取数据时,通过key获取元素
put方法具体流程
- put方法流程
- 扩容
- get方法
HashMap的寻址算法
扰动算法,使hash值更加均匀,减少hash冲突
- 计算hash值
- 二次hash,hashcode右移16位异或运算使得hash更加均匀
- 最后(capacity-1)&hash 得到索引
(n-1)&hash:得到数组中的索引,代替取模,性能更好数组长度必须是2的n次幂
2的n次幂
- 效率更高
- 扩容时,重新计算效率更高
hashmap的扩容原理
扩容条件:
- 当前存储的数量大于等于阈值
- 当某个链表的长度>=8,但数组的节点数size()<64时
特点:先插后判断是否需要扩容(扩容时是尾插法)
扩容之后对table的调整:
table容量变为2倍,但是不需要像之前一样计算下标,只需要将hash值和旧数组长度相与即可确定位置。
-
对于没有hash冲突的节点,通过e.hash&(newCap-1) 计算新索引为位置
-
对于红黑树走红黑树的添加
-
对于链表 遍历链表,可能需要拆分链表,判断e.hash&oldCap是否为0.为零则在原始位置,否则移动到原始位置+增加数组大小的位置上
hashmap不安全的原因
-
非同步操作
多线程情况下导致读写不一致
-
非原子性操作
put()方法包含计算hash,查找和插入,初始化,生成链表,红黑树
-
扩容情况
是将就数组复制到新数组的过程
hashMap在1.7下多线程死循环问题
数组加链表
在扩容过程中,链表使用头插法,在进行迁移过程中,可能导致死循环
解决方法 头插法->尾插法
多线程
基础篇
线程和进程的区别
程序由指令和数据组成,但是这些指定要运行,数据要读写,就必须经指令加载到CPU ,数据加载至内存。在指令运行中还需要用到磁盘,网络等设备。进程就是用来加载指令,管理内存,管理IO等。
一个此案成就是一个指令流,经指令流中的一条条指令以一定的顺序交给CPU执行
一个进程有多个线程
二者对比
- 进程是正在运行的程序实例,进程包含了线程,每个线程执行不同的任务
- 不停的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
- 线程更轻量,线程上下文切换成本一般要比进程上下文切换第
并行和并发的区别
单核CPU
单核CPU下线程实际上还是串行执行的
操作系统有一个组件叫任务调度器
微观串行,宏观并行
多核CPU
每个核core都可以调度运行线程,这时候线程可以是并行的
并发:同一时间应对多件事情的能力
并行:同一时间动手做多件事情的能力
创建线程的方式
继承Thread类
实现runnable接口
实现Callable接口
线程池创建线程
runnbale和callable的区别
- Runnable接口run方法没有返回值
- Callable接口call方法有返回值,是个泛型,和Future,FutureTask配合可以用来获取异步执行的结果
- Callable接口的call()方法允许抛出异常;二runnable接口的run()方法异常内部消化
run和start的区别
run方法可以多次执行
start方法只能执行一次
线程包括哪些状态,状态之间是如何变化的
NEW,RUNNABLE,BLOCKED,WAITING_TIME_WAITING,TERMINATED
线程状态之间是是如何变化的
- 创建线程 new
- 调用start() runnable
- 线程获取到了CPU的执行权 terminated
- synchronized/lock blocked
- wait() waiting
- sleep(50) timed_waiting
新建T1,T2,T3三个线程,如何保证他们按顺序执行
join方法:等待线程运行结束
t2代码块中 t1.join() 方法
t3代码块中t2.join()方法
notify()和notifyAll()
notifyAll()唤醒所有wait线程
notify()随机唤醒一个线程
wait和sleep
共同点
让当前线程防区CPU的是使用权,进入阻塞状态
不同点
- 方法归属不同
sleep–Thread
wait–Object
- 醒来时机不同
sleep(long)和wait(long)等待后醒来
wait可以被notify唤醒
- 锁特性不同
- wait方法调用必须先获取wait的对象锁,sleep无限制
- wait执行后会释放锁对象,允许其他线程获取对象锁
- sleep如果在synchronized代码块中执行,并不会释放对象所
如何停止一个正在运行的线程
- 使用推出标志,正常退出
- 使用stop方法
- 使用interrupt方法终端线程
- 阻塞状态会抛出异常interruptedException
- 正常线程 可以根据打断状态来标记是否退出
安全篇
synchronized底层
Monitor 监视器 由jvm提供 c++实现
-
waitSet 关联调用了wait方法的线程,处于waiting状态
-
entryList 关联没有获取得到锁的线程,处于blocked状态
-
Owner 存储当前获取锁的线程,只有一个线程可以获取
回答
Sync对象锁采用互斥的方式让同一时刻至多一个线程能有持有锁对象
底层是由monitor实现的,monitor是jvm级别的对象,由c++实现
monitor内部由三个属性,owner,entryList,waitSet
owner同一时间只能关联一个线程
进阶
内部的偏向锁,轻量级锁
描述 | |
---|---|
重量级锁 | 底层Monitor实现,涉及到用户态和内核态切换,上下文切换,成本高,性能低 |
轻量级锁 | 线程加锁时间是错开的,没有竞争;可以使用轻量级锁。轻量级锁修改了对象头的锁标志,相对重量级锁性能提升了很多。每次修改都是CAS操作,保证原子性 |
偏向锁 | 一段很长时间都被一个线程使用锁。第一次获取锁,会有一段CAS操作,之后线程再获取锁,只需要判断markdown中是否是自己的id即可 |
JMM(java内存模型)
- JMM模型定义了共享内存中多线程程序的读写规范的行为规范,通过这些规范保证了内存读写指令的正确性
- JMM把内存分为两块,一块是私有线程的工作区域,一块是所有内存的共享区域
- 线程和线程之间是互相隔离,线程和线程交互需要通过主内存
CAS
Compare And Swap 乐观锁思想,无锁的情况下,保证线程操作共享数据的原子性
- AbstractQueueSynchronizer AQS框架
- AtmicXXX类
共享变量时使用自旋锁
CAS底层调用unsafe类中的方法,由系统提供
乐观锁和悲观锁
CAS是基于乐观锁概念,比较和替换
synchronized是悲观锁概念
volatile
共享变量
- 保证线程间的可见性
- 修饰的共享变量会在读写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
AQS
- 抽象队列同步器:构建锁或者其他同步组件的基础架构。像reentrantLock
- AQS内部维了一个先进先出的双向队列,队列中存储的排队的线程
- 再AQS内部还有一个属性state,这个state就相当是一个资源,默认是0,如果队列中的有个线程修改成功为1,则当前线程获取了资源
- state修改的时候使用cas操作,保证多个相乘修改的情况下原子性
sync | AQS |
---|---|
关键字,c++ | java实现 |
悲观锁,自动释放锁 | 悲观锁,手动开启和关闭 |
锁竞争激烈都是重量级锁,性能差 | 竞争激烈情况,提供多种方案 |
ReentrantLock
可重入锁
- 可中断
- 可以设置超时时间
- 可以设置公平锁
- 支持多个条件变量
- 与sync一样,支持重入
原理
CAS+AQS队列实现
支持公平锁和非公平锁,无参为非公平锁,传true设置为公平锁
sync和lock的区别
-
语法层面
sync是关键字,再jvm上由c++实现
lock是接口,由jdk提供,java实现
sync退出代码块自动实现,lock需要调用unlock解锁
-
功能层面
二者均为悲观锁,具备互斥,同步,可重入等功能
Lock存在sync不具备的功能,如公平锁,可打断,可超时,多条件变量
Lock有不同场景的实现ReentrantLock,ReentrantReadWriteLock
-
性能层面
在没有竞争时,sync做了很多优化,入偏向锁,轻量级锁
在竞争激烈时,Lock实现会提供更好的性能
死锁产生的条件
一个线程同时获取多把锁,造成死锁
如何诊断死锁
- jps:进程状态
- jstack:进程堆栈信息
- 可视化工具,visualVM,Jconsole
concurrentHashMap
高效Map集合
CAS 添加数据+sync 锁住首节点
导致并发程序出现问题的根本原因
java并发编程三大特性:
-
原子性
-
可见性
-
有序性
线程池
核心参数
-
corePoolSize 核心线程数
-
maximumPoolSize 最大线程数
-
keepAliveTime 生存时间
-
unit 时间单位 - 救急线程生存时间
-
workQueue 当没有空闲核心线程时,新来任务会加入此队列排队,队列满了会创建救济线程执行任务
-
threadFactory 线程工厂
-
hander 拒绝策略
执行原理
拒绝策略
- AbortPolicy 抛异常
- CallerRunsPolicy 用调用者的所有线程执行
- DiscardOldestPolicy 丢弃阻塞队列种最靠前的任务,并执行当前队列
- DiscardPolicy 直接丢弃任务
线程中常见阻塞队列
ArrayBlockingQueue 有界阻塞队列
LinkedBlockingQueue 单向链表
DelayedWorkingQueue
SynchronousQueue
Array | Linked |
---|---|
强制有界 | 默认无界,支持有界 |
底层链表 | 底层数组 |
惰性,创建节点添加数据 | 初始化Node数组 |
两把锁 - 头尾 | 一把锁 |
如何确定核心线程数
- IO密集型任务 核心线程数:2N+1
文件读写 DB读写 网络请求
-
CPU密集任务 核心线程数:N+1
计算型代码,Bitmap转换,Gson转换
(N为CPU数量)
线程池的种类
-
创建固定线程数的线程池
newFixedThreadPoolExector
-
单线程话的线程池
newSingleThreadPoolExector
-
可缓存线程池
newCacheThreadPoolExcutor
-
延迟和周期执行功能线程池
ScheduledThreadPoolExcutor
为什么不建议用Executors创建
允许请求的长度为integer.MAX_Value ,可能导致消息堆积,导致OOM 内存溢出
使用场景
线程池的使用场景,在你的项目中哪里用到了线程池
场景1:数据导入
使用了线程池+CountDownLatch批量把数据库中的数据导入到了(ES)中,避免OOM
闭锁/倒计时锁
用来进行线程同步协作,等待所有线程完成倒计时
- 参与构造参数用来初始化等待计数值
- await()用来等待计数归零
- countDown()用来让计数减一
场景2:数据汇总
调用多个接口来汇总数据,如果所有接口都没有依赖关系,我们可以使用线程池+future来提升性能
- 在实际开发过程中,难免需要调用多个接口来汇总数据,如果所有接口都没有依赖关系,我们就可以使用线程池+future来提升性能
- 报表汇总
场景3:异步调用
为了避免下一级方法影响上一级方法(性能考虑),可以使用异步线程调用下一个方法,可以提升方法响应时间
如何控制某个方法允许并发访问的数量
Semaphore 信号量 JUC包下的工具类 底层AQS 通常限制执行线程的数量
通常用于资源有限的场景,常用于限流
使用
- 创建Semaphore对象,可以给一个容量
- acquire()可以请求一个信号量,这个时候的信号量个数-1
- release()释放一个信号量,此时信号量个数+1
ThreadLocal
多线程中对于解决线程安全的一个操作类,他会为每一个线程都分配一个独立的线程的副本从而解决了线程冲突的问题。threadlocal同时实现了线程内资源共享
基本使用
- set(value) 设置值
- get() 获取值
- remove()清除值
底层实现
本质上是一个此案成内部存储类,从而让多个线程操作自己的内部值,从而实现线程数据隔离
ThreadLocal-内存泄漏问题
java对象中的四种引用类型:强引用,软引用,弱引用,虚引用
弱引用:GC会被干掉
k是弱引用 v是强引用 导致内存泄漏
解决方面 使用remove方法
JVM
java二进制字节码运行环境
JVM组成
程序计数器
线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码的地址
javap -v xx.class 打印堆栈大小
java堆
-
线程共享区域:主要保存对象实例,数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常
-
组成:年轻代和*+*老年代
- 年轻代:Eden区和两个大小严格相同的Survivor区
- 老年代:保存生命周期长的对象
-
jdk1.7和1.8的区别
1.7中有永久代
1.8去除了永久代,放到了原空间中,防止内存溢出
虚拟机栈
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈有多个栈帧组成,对应着每次方法调用时所占用的内存
- 每个线程只能由一个活动栈帧,对应着当前正在执行的哪个方法
垃圾回不涉栈内存
栈内存越大越好吗?
未必,默认是1024k。栈过大会使得线程数变少。
方法内的局部变量是否是线程安全的?
如果方法内的局部变量没有逃离范围,是线程安全的
栈内存溢出情况
- 栈帧过道导致内存溢出,如 递归
- 栈帧过大导致栈内存溢出
堆和栈
- 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来春初java对象和数组的。堆会GC垃圾回收
- 栈内存是私有的,堆内存是线程共有的
- 空间不足时 栈:StackOverFlowError 堆:OutOfMemmoryError
方法区
各个线程共享的内存区域
主要存储类的信息/运行时常量池
虚拟机启动时创建,关闭时释放
空间不足:OutOfMemmoryError:Metaspace
常量池
可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
javap -v xx.class
当类被加载,他的常量池就会放入到运行时常量池,并把符号地址变为真实地址
直接内存
并不属于JVM中的内存结构,不由JVM进行管理。是续集的系统内存,常见于NIO操作时,用于数据缓冲区,它分配回收成本较高,但读写性能高
类加载器
JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来
类加载器有哪些
- 启动类加载器
- 扩展类加载器
- 应用类加载器
- 自定义类加载器
双亲委派模型
加载某一个类,现委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类。
为什么使用双亲委派机制
- 通过双亲委派机制可以避免某一个类被重复加载,当弗雷已经加载后则无需重复加载,保证唯一性。
- 为了安全,保证类库API不会被篡改
类装载的执行过程
-
加载
- 通过类的全名,获取类的二进制数据流
- 解析类的二进制数据流为方法区内的数据结构
- 创建java.lang.Class类的实例,表示该类型。作为方法去这个类的各种数据的访问接口
-
验证
验证类是否符合JVM规范,安全性检查
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
-
准备
为类变量分配内存并设置类变量初始值
- static变量,分配空间再准备阶段完成,设置默认值,复制再初始化阶段完成
- static变量是final的基本类型,以字符串常量,值以确定,复制在准备阶段完成
- static变量是final的引用类型,复制在初始化阶段完成
-
解析
把类中的符号引用变为直接引用
-
初始化
对类的静态变量,静态代码块执行初始化操作
- 如果初始化,父类未初始化,先初始化父类
- 多个静态代码块,顺序加载
-
使用
- 调用静态类成员信息
- 使用new关键字为其创建成员信息
-
卸载
JVM销毁
垃圾回收
对象什么时候可以被垃圾回收
-
引用计数法
一个对象引用一次,在当前对象头上递增一次引用次数,如果这个对象的引用次数为0,这表示可以被回收
循环引用,导致OOM
-
可达性分析算法
可以作为GC Root对象
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
JVM垃圾回收算法有哪些
- 标记清除算法
- 标记整理算法
- 复制算法
JVM分代回收
分代手机算法
新生代:老年代=1:2
Eden:from:to=8:1:1
Eden几次回收仍然存活,晋升到老年代
MinorGC,MixedGC,FullGC
MInior GC 新生代垃圾回收 暂时时间短
Mixzed GC 新生代+老年代部分区域 垃圾回收 ,G1收集器特有
Full GC 新生代+老年代完整垃圾回收,暂停时间长,尽量避免
JVM垃圾回收器
- 串行垃圾收集器 Serial GC ,Serial Old GC
- 并行垃圾收集器 Parallel Old GC,ParNew GC
- CMS(并发)垃圾收集器 CMS GC,作用在老年代
- G1垃圾收集器 新生代/老年代
G1
- 应用于新生代和老年代 JDK9之后默认
- 划分很多区域,每个区域都可以充当eden,surviver,old,humongous
- 采用复制算法
- 相应时间和吞吐量兼顾
- 分为三个阶段:新生代回收,并发标记,缓和收集
- 如果并发失败描绘出发Full GC
强引用,软引用,弱引用,虚引用
内存不足,软引用被垃圾回收
垃圾回收时,弱引用被回收
虚引用需要配合引用队列释放资源
JVM实践
JVM调优
-
war
修改TOMCAT_HOME/bin/Catalina.sh文件
-
jar
java -Xms512m -Xmxm -jar xxx.jar
JVM调优参数
-
设置对空间大小
-Xms 设置堆初始大小 最大物理内存 1/16
-Xmx 设置堆最大大小 最大物理内存 1/4
-
虚拟机栈
-Xss 对每个线程stack大小的调整 -Xss128k
-
Eden Survivor 设置比例
-XXSurvorRatio=8 survivorLeden=2:8
-
JVM 设置垃圾回收器
-XX:+Use Parallel GC
-XX:+UserParallelOldGC
JVM调优工具
-
命令工具
jps 进程状态信息
jstack 查看java进程中线程堆栈信息
jmap 生成堆转内存快照 内存使用情况 jmap=heap pid 显示java堆信息
jstat 统计监测工具,显示垃圾回收信息,类加载信息,新生代统计信息
-
调优工具
jconsole 查看内存,线程,类等
VisuaVM
内存泄漏排查思路
- 通过jmap或者设置jvm蚕食获取堆内存快照dump
- 通过工具,VisualVM区分析dump文件,VisualVM可以加载离线的dump文件
- 通过查看堆信息的情况,可以大概定位内存溢出的那行代码出的问题
- 找到对应的代码,通过阅读上下文的情况,进行修复即可
CPU飙高排查方案和思路
- 使用top命令查看占用的情况
- 通过top命令查看后,可以查看时哪一个进程占用cpu较高
- 使用ps命令查看进程中的线程信息
- 使用jstack命令查看进程中哪些线程出现了问题,最终定位
企业场景
设计模式
工厂方法模式
核心目的:解耦
简单工厂模式
所有产品共用一个工厂,新增产品要修改代码
工厂方法模式
开闭原则:对扩展开放,对修改关闭
为每个产品提供一个工厂,让工厂专门负责对应的餐品的生产
抽象工模式
产品的多个维度要配合生产,工厂的工厂
策略模式
- 该模式定义了一系列的算法,并将每个算法封装起来,使他们可以互相替换,且算法的变化不会影响使用算法的客户
- 它通过对算法进行封装,把使用算法的责任和短发的实现分割开来,并委派给不同的对象对这些算法进行管理
角色
-
抽象策略Strategy
抽象角色,通常由接口/抽象类实现
-
具体策略Concrete Stratrgy 实现接口,提供算法实现和具体行为
-
环境Context 持有策略的引用,最终给客户端调用
优劣
优势
- 策略类之间可以自由切换
- 易于扩展
- 避免使用多重条件选择语句,充分体现面向对象的设计思想
缺点
- 客户端必须知道所有的策略类,并决定选用哪个
- 测类模式将产生很多策略
登录案例(工厂模式+策略模式)
只要代码中由冗长的if-else或switch分支判断都可以采用策略模式
责任链设计模式
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的矗立着通过迁移对象记住其下一个对象的引用二连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
角色
- 抽象处理者
- 具体处理者
- 客户类
优劣
优点
- 降低对象之间的耦合度
- 增强系统的可扩展性
- 增强了给对象指派责任的灵活性
- 责任链简化了对象之间的链接
- 责任分担
缺点
- 对于较长的责任链设计多个对象处理,影响性能
- 简历合理性要求由客户端来保证,增加客户端的复杂性
场景
-
内容审核
视频,文章,课程
-
订单创建
校验-填充-算价-落库
-
简历流程审批
组长=主管=副总裁
常见的技术场景
单点登录
Single Sign On (SSO),一次登录,全部信任
方案
- JWT
- 用户访问其他系统,会在网关判断token是否有效
- 如果token无效则返回401,前端跳转登录页面
- 用户发送登录请求,返回浏览器一个token,浏览器将token保存到cookie中
- 在其访问其他服务时,携带token,由网关同意验证后路由到目标服务
- Oauth2.0
- CAS
权限认证
用户 角色 权限 (菜单)
权限框架
- Apache Shiro
- Spring Security
上传数据的安全性
使用非对称加密(对称加密),给前端一个公钥让他把数据机密后传到后台,由后台解密。
- 大文件使用对称加密
表述棘手的问题
- 什么背景 技术问题
- 过程 解决问题的过程
- 最终落地方案
cord死锁问题,ats内存优化问题
日志采集
可以根据日志信息采集系统问题
方式
-
ELK
-
Elasticasearch 全文搜索引擎
-
Logstash 数据手机引擎
-
Kinbaba 数据分析和可视化平台
logback-spring.xml 配置Logstash
-
-
常规采集
查看日志命令
linux
-
实时监控日志的变化
tail -f xx.log
最后100行 tail -n 100 xx.log
-
按照行号查询
查询100至200行 cat -n xx.log|tail -n +100|head -n 100
-
按照关键字找日志的信息
cat -n xx.log|grep “dubug”
-
按照日期查询
sed -n ‘/2024-03-10 10:38:1031.070,/2024-03-10 10:38:1031.070/p’ xx.log
-
日志太多,处理方式
分页查询 cat -n xx.log|grep “grep”|more
生产环境问题怎么排查
已经上线的bug排查的思路:
-
线分析日志,通常在业务中都会由日志的记录
-
远程debug
远程代码和本地代码保持一致
- 远程diamagnetic需要配置启动参数
java -jar -agentlib:jdep=transport=dt_socket,server=y,suspend=n,address=5005 xx.jar
- idea中启动远程debug
- 访问远程服务
快速定位系统瓶颈
- 压测 性能测试
- 目的 给出系统当前的性能状况;定位系统性能瓶颈
- 指标 响应时间,QPS,并发性,吞吐量,cup利用率,内存使用率,磁盘IO,错误率
- 压测工具 jmeter
- 后端工程师 配合QA调优
- 监控工具,链路追踪工具
- 监控工具 Prometheus+Grafana
- 链路追踪工具 skywalking
- 线上诊断工具Arthas
- ali的
- 功能强大
项目亮点 基础实现
系统可用性(滴滴)
对于请求如何保证999的成功率
互联网架构中,通常是通过冗余+自动故障转移来保证系统的高可用特性。
一:服务架构层面
- 根据服务对象地区,考虑节点分布
- 避免服务单点,至少双机
- 防止代码之间干扰,避免稳定代码和迭代频繁代码放在一起,可以按照业务或者功能做服务分离。
- 防止服务之间干扰,重要服务最好做隔离,单独部署
- 防止数据库压力过大,不然,可能产生雪崩效应,可以根据业务特点做分库分表,加缓存等处理.
- 保证服务能力buffer, 尽量有冗余处理能力.
二:运维层面
- 服务监控。比如磁盘、CPU、网络
- 监控多级别,到达不同级别给出不同警告
三:代码层面
- 保证代码异常不会导致服务挂掉
- 保证服务是无状态的,可以支持水平扩展
防脚本策略(某AI公司)
后端幂等性校验
前端人脸识别,滑块
基础
IO
分类
阻塞&同步分类
同步阻塞 BIO
同步非阻塞 NIO
异步 AIO
java分类
字节流 inputStream outputStream
字符流 Writer Reader
基于磁盘的问渐渐IO
基于网络的socker-IO
面试
自我介绍
我叫Bryan,今年29岁,2018年毕业与青海大学,青海大学是一所211院校。java开发岗位工作6年。熟悉 前后端分离项目 技术栈:SpringBoot、SpringCloud、mybatis、redis 、kafka、ELK 等,数据库mysql/Oracle,部署工具Jenkins,AWS,linux熟练使用。
之前在高知特担任java开发的工作,主要负责代码设计,开发实现,数据库设计优化,线上问题紧急修复,业务梳理等工作,
项目介绍
技术亮点
-
再有就是目前比较新的spring cloud之后新的模式,配置从开发端移除的一个概念性的微服务架构 side-car
-
同步转异步的方式 加快接收速度 减慢处理速度
-
Azure就是配置中心 和storage存储
-
云存储 S3 BOX
-
一版就是AWS和微软的Azure 整个CICD流程
-
snowflake/salesforce
结构亮点
结合腾讯云做钩子函数 将链接发给用户
面试心得
- 面试时 一定要想好了在回答 梳理自我介绍/项目介绍
- 开心自信 这个很重要 整体氛围感要有
- 休息好了再去 最好是休息一下再去面试 保证最好的状态
- 硕士 加分项 未来准备
- 英语 加分项 未来准备