SpringSecurity
说说 CSRF
-
CSRF(cross site request Forgery) 简称跨站请求伪造
, 就是伪造用户向已经授权的web网站发送恶意请求 [比如用户刚刚登陆一个银行网站A, 但是在这个网站 cookie 还没过期的时候, 访问钓鱼网站B, B引导用户点击链接向A发送转账后请求, 因为用户浏览器与网站A的Cookie没有过期, 此时用户发送的请求时不需要验证身份的, 利用网站对用户的信任, 进行伪造请求]
-
预防方式一般有2种, 首先, 通过 http 请求头中的 referer 字段, 验证发送请求的网站是不是受信任的网站, 其次, 通过身份令牌token值, token可以有三种携带方式, 放在cookie中, 但是无法跨域, 放在请求头中的Authorization字段中, 也可以放在表单隐藏域中
https://www.cnblogs.com/Samforrest/p/15881605.html
https://www.jianshu.com/p/91d30b7daa33
# 为什么token放在cookie中可以防止CSRF
https://blog.csdn.net/qq_39739740/article/details/126735344
说说 XSS
-
XSS 是跨站脚本, 主要是在游览器中执行恶意脚本获取非法信息, 主要可以 ① 获取cookie ② 监听用户行为 ③ 伪造表单 ④ 生成浮窗广告等
-
XSS 主要包括存储型, 反射型, DOM文档型
https://blog.csdn.net/deniro_li/article/details/89054229
https://blog.csdn.net/m0_57965131/article/details/125583712
大数据
谈谈你对 hadoop 的认识
-
Hadoop是一个分析和处理大数据的软件平台, 他主要利用服务器集群, 根据用户自定义的业务逻辑, 对海量数据进行分布式处理
-
hadoop有三个核心组件, 分别是 hdfs, yarn, MapReduce, 可以用来存储含海量数据, 处理含量数据, 分析海量数据
-
hadoop生态圈可以理解为操作系统, hdfs, yarn, MapReduces 是核心, 其它类似HBASE, Hive, zookeeper可以理解为软件
https://mp.weixin.qq.com/s?__biz=MzA3MDc1MDcxNQ==&mid=2447687947&idx=1&sn=ce94d95064666f43a83b3260065f0f02&chksm=8b28799fbc5ff089be9509fa29db093df185fa2ceb416fb254ed06d17b4f0d83c5cf7b00a4da#rd
https://blog.csdn.net/qq_37006625/article/details/123442143
说一说 MongoDB
-
MongoDB 就是文档型数据库, 提供了丰富的查询功能, 主要支持对json的操作
-
MongoDB 还被誉为最像SQL的NoSQL数据库, 因为它支持事务, 支持索引
-
MongoDB 操作的数据主要是在内存中, 因此速度客观, 还支持数据持久化
-
MongoDB是key-value模式, 还支持二进制数据和大型对象存储
谈谈你对 hbase 的认识
-
Hbase 是面向列式的分布式存储数据库, 底层依托与 hdfs, 通过zookeeper进行集群管理
-
列式数据库的查询不需要像行式数据库一样查询某一字段的时候查询整张表, 我们可以针对某一字段进行查询, 可以提高效率
-
因为列式数据库是按字段列存储数据的, 字段大小相似, 我们可以使用压缩进行空间节约
https://mp.weixin.qq.com/s?__biz=MzA3MDc1MDcxNQ==&mid=2447688478&idx=1&sn=01eaf534ee84bdf6dee136ee9003eac2&chksm=8b287b8abc5ff29c0750ac1b820aa66aa7995f76b274d809dbb2de9d38ffc5178d991f269478#rd
https://blog.csdn.net/weixin_43958974/article/details/124698891
谈谈你对Hive的认识
-
Hive 是一个基于hadoop的数据仓, 他可以把HDFS中结构化的数据映射成表, 也可以把HiveSQL进行解析转化生成可以在MapReduce任务在hadoop是执行
-
引入Hive的原因是让不懂MapReduce代码的程序员也可以快速上手MapReduce的使用
http://t.zoukankan.com/fishperson-p-10492611.html
分布式&集群
什么是集群, 我们为什么使用集群
-
集群是一组相互独立的计算机, 通过高速通信网络组成的一个较大的计算机服务器, 集群中的节点都是运行着各自服务的独立服务器, 他们彼此通信, 相互协作向用户提供应用程序, 系统资源及数据, 并通过单一系统模式进行管理, 当用户发送请求时, 给用户的感觉是他面对的是一个单一独立的服务器, 实际上面对的是集群服务器
-
使用集群的好处是高性能, 因为可以利用多台计算机的计算能力, 其次是廉价且实用, 每台计算机进行组合比超级计算机便宜但计算能力却不差, 再者就是高可用且可伸缩, 当集群中某台服务器宕机,不会影响这个集群, 集群可以7*24小时工作, 添加减少集群十分容易
什么是分布式, 为什么使用分布式
-
分布式是微服务的一种体系结构, 就是将一个完整的系统, 按照不同的业务功能, 拆分成不同的子系统, 每个子系统就是一个"服务", 这些子服务可以运行在容器中, 通过RPC进行通信, 向外提供完整服务
-
RPC(Remote Procedure call)就是远程过程调用 , 他屏蔽网络编程的细节, 让程序向调用本地接口一样调用远程接口, 一般使用TCP作为底层传输协议, 有client, client stub, Server stub, Server结构
-
使用分布式可以降低模块间耦合, 独立开发, 提高开发效率, 避免单模块宕机而整个模块不可用现象
集群与分布式的区别和联系
-
集群是串联工作, 同一个子业务系统复制成多份,将其分别部署在多台服务器上, 分布式是并行工作, 是将一个复杂的业务系统,拆分成多个子业务系统
-
分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率
-
分布式中某个热点子业务系统其实也是可以复制成多个实例,采用集群的模式进行部署
讲一下你对分布式数据库的理解
-
分布式数据库可以理解为把数据存储在不同物理位置的数据库, 使用分布式数据库我们可以做到职责分离, 平滑扩容, 提高负载和容错能力
-
可以把分布式数据库分为 计算层, 元数据层, 存储层
-
计算层就是SQL层, 可以对数据访问的权限检查, 路由访问等
-
元数据层可以存储节点信息,
-
存储层就是数据存储的位置了
-
我们操作分布式数据库可以借助中间件作为计算层, 中间件屏蔽底层细节, 使我们想面对集中式数据库一样操作数据而不关心器实现原理
简略
https://zhuanlan.zhihu.com/p/408106925
详细
https://www.zhihu.com/question/538296497/answer/2655443035?utm_id=0
网络知识
OSI七层网络模型
说一说TCP的三次握手与四次挥手
-
三次握手
-
第一次握手, 客户端发送SYN包到服务器, 自己进入SYN_SEND状态
-
第二次握手, 服务器收到SYN包, 发送SYN和ACK包, ACK 是客户端SYN的确认, 自己进入RECV状态
-
第三次握手, 客户端收到SYN和ACK, 最后发送一个ACK, 对服务器的SYN进行确认, 进入ESTENBLISH状态
-
四次挥手
-
第一次挥手, 主机A发送FIN报文段给主机B
-
第二次挥手, 主机B发送ACK给主机A, 同时通知应用程序关闭程序释放资源
-
第三次挥手, 主机B发送FIN给客户端A
-
第四次挥手, 主机A收到FIN后回复ACK就关闭连接
说一说Http与Https
-
http是超文本传输协议, 是一种客户端与服务器请求应答的标准, 依靠TCP和网络与服务器建立连接, 一般为80端口, http 报文一般包括请求头, 请求体, 请求行, 服务器也是响应http报文, 包括状态码, 响应体信息等
-
https还是http通道, 只不过添加了SSL进行安全加强, 在tcp/ip 与各种应用层之间, 最大区别就是http是明文传输, 是无状态的, https使用了加密协议, 端口也变成了443
https://blog.csdn.net/qq_38289815/article/details/80969419
https://blog.csdn.net/LINUX_THINKMO/article/details/123761301
TCP与UDP的区别
-
TCP 是一种面向连接的, 可靠的, 基于字节流的传输控制层双工协议, 目的是为了保证数据传输的安全性, 有三次握手, 四次挥手, 还有短连接与长连接
-
UDP就是是无连接的,没有拥塞控制,面向报文不可靠连接
https://blog.csdn.net/m0_70748381/article/details/124710296
https://blog.csdn.net/hayre/article/details/103041478
哪些问题是HTTPS无法解决的
-
http 是基于TCP的, 网络传输耗时长
-
Http头不能压缩, 每次都需要传递很大头数据包, 一次连接也只能处理一个请求
-
Https 用了很多加密算法, 也会影响速度
常见的HTTP协议请求头有哪些
-
referer 表示请求地址
-
Host 服务器域名
-
Cookie cookie信息
-
Accept 接受的返回数据类型
-
Accept-Encoding 接受的编码类型
-
Accept-Charset 接受的字符集类型
-
Cache-conctrol 是否缓存
-
Connection 连接类型
TCP协议的首部结构
-
TCP 是传输层协议, 头部包括20字节固定长度与40字节可选长度
-
20字节固定长度为:
-
源端口
-
目的端口
-
序号
-
确认号
-
数据偏移 保留 SYN、ACK、FIN等 窗口
-
校验和 紧急指针
IP协议的首部结构
-
IP是网络层协议, 头部包括20字节固定长度和40字节可选长度
-
20 字节固定长度包括:
-
版本号 首部长度 服务类型 数据长度
-
标识 标志 片偏移
-
生存时间 协议 首部校验和
-
源地址
-
目的地址
Linux
列举Linux常用命令,不能是ls的简单命令
-
ps -aux 显示进程号
-
systemctl stop firewalld.service 关闭防火墙
-
crond -e 开启定时任务
-
chgrp -g groupName UserName 修改用户所在组
-
chmod 777 filename.txt 修改文件权限
如何打开一个大文件
-
打开一个大文件的关键在于不能直接将文件中的数据全部读到内存中, 会引起OOM
-
如果读取的文本文件, 可以使用Scanner逐行读取
-
如果读取的二进制流文件, 可以使用缓冲流分段读取
说说你对 ThreadLocal 的理解
-
ThreadLocal 是线程变量, 他的实现与同步机制不同, 他是为每一个线程开辟专属空间, 复制一份副本, 因此不存在并发问题
-
TreadLocal 底层维护了TreadLocalMap类型的TreadLocals变量, 类似于Map, 存放键值对, 通过set存储线程变量, 通过get取出与当前线程绑定的变量
-
TreadLocal在线程池中需要手动清除变量, 不然会造成内存泄露问题
说说你对线程池的理解
-
线程池技术可以有效的管理线程数量, 我认为他可以有两个方面的核心
-
避免无节制的创建线程超出系统负荷, 导致系统崩溃
-
同时支持线程复用, 减少系统的资源的消耗
-
线程池主要参数包括: ① corePoolsize(核心线程数) ② workQueue(工作队列) ③ maxinumPoolsize(最大线程数) ④ handler (拒绝策略) ⑤ keepAliveTime(空闲线程存活时间)
-
向线程池提交任务之后执行步骤为: 检查当前线程是否达到核心线程数, 没有就创建执行, 达到的话就检查工作队列是否已满, 没有就加入工作队列, 满了就检查是否达到最大线程数, 没有就创建执行, 满了就按照拒绝策略进行任务拒绝执行, 当前线程执行完毕后如果工作队列中也没有数据, 就进行阻塞, 如果超过空闲线程最大存活时间就就缩小线程数为核心线程数
-
拒绝策略主要包括4种, 分别为①让调用者自己处理②直接抛出异常③直接抛弃不处理④将任务替换工作队列中最老的任务
-
线程池的生命周期有五种状态
-
RUNNABLE 表示线程池正在运行
-
SHUTDOWN 通过shutdown()的调用进入, 此时不会清空队列, 线程池会等待任务执行完毕
-
STOP 通过 shutdownnow() 进入, 会清空队列, 不在等待任务执行
-
TIDING 线程池与队列为空进入, 此时会执行一个空实现的钩子函数
-
TERMINATED 执行完钩子函数后进入
https://www.nowcoder.com/exam/interview/detail?questionClassifyId=0&questionId=2412496&questionJobId=160&type=1
https://www.cnblogs.com/mic112/p/16306067.html
JDK三种创建线程的方式
-
继承Thread类, 重写run()方法, 实现简单(实例化即可), 单继承限制了扩展性
-
实现Runnable接口, 重写run()方法, 构造线程实例比较复杂, 可以提高扩展性, 共享线程资源
-
实现Callable接口, 重写call()方法, 实现复杂, 有返回值,还可以抛出受检异常
说说线程的状态
-
java的线程状态被定义成枚举型, 线程状态主要有6种, 包括NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WTING, TERMINATED
-
NEW 是线程刚被创建的新生状态
-
RUNNABLE 是调用start()方法后的就绪或运行状态
-
BLOCKED 是等待获取监控器锁的阻塞状态
-
WATING 是调用wait()或sleep()进入的等待状态, 需要调用notify()或interrupt()进行唤醒
-
TIMED_WTING 是等待状态加了超时机制, 等待超时就会返回
-
TERMINATED 是程序运行结束或异常结束进入的终止状态
-
线程阻塞与synchronize是阻塞状态, 阻塞与Lock是等待状态, 因为LOCK对阻塞式基于LockSupport实现的
说说你了解的线程同步方式
-
线程同步可以加锁, 加锁有两种方式, 分别是synchronize和lock
-
synchronize和Lock的参考详解
说说synchronize的用法及原理
-
synchronized可以作用在三个不同的位置, 三种位置意味着锁对象不同, 不同的锁对象,意味着不同的锁粒度
-
作用在静态方法上,则锁是当前类的Class对象
-
作用在普通方法上,则锁是当前的实例
-
作用在代码块上, 可以显示指定一个锁对象
-
synchronize使用对象头来存储锁信息, Java对象头包含三部分,分别是Mark Word、Class Metadata Address、Array length
-
synchronize为了减少锁开销, 还支持锁升级
说说Lock的用法及原理
-
Lock 需要 lock() 和 unLock() 手动加锁和释放锁, 比较灵活
-
Lock是jdk1.5引入的, 解决了synchronize早期的问题, 包括支持相应超时, 中断机制, 异常处理和多变量问题
-
lock的实现类都是基于AQS实现的,AQS是基于模板方法实现的, 因此锁的实现要继承AQS并重写其指定的方法
-
Lock还需要依赖AQS的状态变量来保存锁的信息, 同时自己也需要依赖Condition对象进行线程通信
说说你对AQS的理解
-
AQS是同步队列器, 是基于模板方法模式实现的
-
AQS 内部定义了一个FIFO的双向队列来实现线程同步, 维护的state状态量=0表示无锁, >0表示可重入次数, 用来存储锁信息
-
AQS的模板方法可以分为4种, 分别是独占式获取同步状态, 独占式释放锁状态, 共享式获取锁状态, 共享式释放锁状态
说说进程和线程的区别
-
进程是有地址空间的, 也有内存空间. 而线程虽然有自己的栈堆和局部变量, 但没有地址空间, 除了CPU, 系统不会他分配内存空间
-
进程不适合用并发, 线程适合做并发
-
进程与线程进行上下文切换的时候, 进程的消耗要比线程更大
-
进程有一个函数入口, 顺序执行序列, 出口. 所以进程可以独立运行, 但是线程不可以, 必须依赖进程启动
-
进程崩溃的时候, 由于保护模式, 不会对其它进程造成影响, 但线程一旦崩溃, 会导致整个进程崩溃
说说多线程
-
线程是操作系统调度的最小单元, 他可以让一个进程处理多个任务, 所以线程也被称为轻量级进程
-
线程有自己的程序计数器, 堆和栈空间, 以及局部变量, 多个线程共享进程内存资源, 使得处理器可以在线程间进行快速切换, 达到并发的效果
-
我们使用线程的原因:
-
首先是现在计算机已经从追求更高主频转向更多核心发展, 使用多线程可以更好利用CPU资源
-
其次是使用多线程可以提高处理速度, 面对用户的并发请求, 多线程处理可以有更短相应时间, 给用户的体验感更好
说说死锁定义及发生的条件
-
死锁是指多个线程争抢共享资源造成循环等待现象, 没有外力干预, 就无法向前推进, 造成死锁必须具备四个条件
-
① 资源的互斥性, 多线程争抢的资源在同一时间只允许一个线程持有
-
② 请求和保持条件, 一个线程在持有一份互斥资源的情况下, 还需要请求其它互斥资源
-
③ 不可剥夺条件, 一个线程持有的互斥资源, 除非主动释放, 否则不允许其他渠道进行强制释放
-
④ 循环等待条件, A在等待B占有的资源, B在等待C占有的资源, C在等待A占有的资源, 造成环路现象
-
解决死锁的办法可以从后三个条件入手, 注意, 互斥条件无法破坏, 因为他是互斥锁的基本约束
-
请求和保持条件, 在首次请求的时候一次请求所有资源, 就不存在等待锁的问题
-
破坏不可抢占条件, 当线程申请其它资源但是等不到的时候主动释放他占有的资源
-
循环等待条件, 给资源进行有序申请, 只有申请前一个资源才可以申请后一个资源
说说进程间的通信方式
-
进程间通信方式有几种, 包括 ①管道, ②命名管道, ③信号, ④共享内存, ⑤内存映射, ⑥信号量, ⑦消息队列, ⑧socket
-
管道又称匿名管道, 用于具有亲缘关系进程间的通信, 本质是内核中维护的一块缓冲区, 通过pipe()函数创建管道, 同时生成两个文件描述符, 用于读写
-
命名管道又称FIFO文件, 会生成一个路径名, 与命名管道相关联, 能够访问路径名的进程可以进行通信, 解决匿名管道只能用于亲缘进程间通信的弊端
-
信号就是一个运行进程被一个运行的异步进程打断, 转而处理某一突发事件
-
共享内存是多个通过共享物理内存进行数据交换
-
内存映射就是将磁盘文件映射到内存, 修改内存就是修改磁盘文件
-
信号量是通过PV操作对信号量进行加减操作, 当信号量为0时, P会被阻塞, 除非有进程进行了V操作
-
消息队列就消息链表, 对消息队列有读写权限的进程才可以对队列进行生产和消费消息
-
socket主要用于网络上两台不同主机的不同进程之间进行端点通信
说说你了解的线程通信方式
-
线程间通信主要通过 Monitor(同步监视器), 以及Condition对象
-
关于 Monitor 相关API主要是在 Object 中的 wt(), notify(), notifyAll()
-
关于 Condition 相关 API 在本身的 await(), signal(), signalAll()
https://www.nowcoder.com/exam/interview/detail?questionClassifyId=0&questionId=2412456&questionJobId=160&type=1
说说虚拟内存和物理内存的区别
-
物理内存是计算机真实存在的内存, 虚拟内存是计算机内存管理的一种方式, 是一种抽象的概念
-
物理内存容易因为内存不足造成阻塞问题和不安全问题
-
虚拟内存可以认为是一段连续的地址空间, 但实际上可能映射的是多个物理内存碎片, 可能还存在外存上
说说内存管理
-
Linux内存采用段页式内存管理的方式, 分页内存管理可以提高内存利用率, 分段式内存管理可以反应程序的逻辑结构和段的共享, 两者结合就形成了段页式内存管理
-
段页式将程序分为若干段, 段中又分为若干页, 为了实现逻辑地址到物理地址的转化, 必须维护至少一张段表和一张页表
-
从段表中找出页初号, 找到对应的页中的页帧号, 形成物理地址
说说BIO、NIO、AIO
-
UNIX 有五种内存模型, 分别是 阻塞IO, 非阻塞IO, IO复用, 异步IO, 信号驱动IO, 最常用的是IO复用
-
BIO是同步并阻塞IO, 一个线程对应一个连接, 有消息就处理, 没有就阻塞
-
NIO是同步非阻塞, 将连接注册到多路复用器上, 同时轮询连接, 有消息才处理
-
AIO是异步非阻塞, 有效请求才启动线程, 由操作系统完成后才通知服务端
说说IO多路复用
-
IO多路复用是指一个应用程序可以监听多个文件描述符(句柄), 本质就是使用内核缓存多个IO数据, 使单线程可以处理多个IO请求
-
Linux实现IO复用的系统调用包括select, poll, epoll
-
select是将装载有文件描述符的列表返回, 底层实现是数组, 实现方式是轮询+全局扫描, 客户端在操作服务器时, 会创建3中文件描述符fd, 分别是写fd, 读fd, 异常fd, select 会监视这三种文件描述符, 当有数据可以触发这3中描述符的时候返回, 通过遍历fd_set找到就绪的fd, 触发相应的io操作
-
poll 和select的的原理相似, 但底层是链表, 装载的fd没有上线
-
epoll是更加高效的IO多路复用, 底层使用了红黑树存储需要检测的文件描述符信息, 双向链表存储检测到数据发生改变的文件描述符的信息, 采用的是时间通知机制触发IO操作
https://www.nowcoder.com/exam/interview/detail?questionClassifyId=0&questionId=2412616&questionJobId=160&type=1
epoll原理
-
epoll是一种更加高效的IO复用技术, 底层包括红黑树和双向链表, 采用的是时间通知机制触发IO操作, 主要通过调用系统函数注册激活fd
-
epoll通过epoll_create() 创建epoll对象, 在Linux内核中创建B+树, 包含两个成员分别是红黑树, 存储需要检测的文件描述符的信息, 双向链表存储到是数据发生改变的文件描述符的信息
-
通过epoll_ctl() 向epoll对象增删改所监听的文件描述符, 并绑定一个回调函数callback
-
通过epoll_wt() 会轮询所有的callback函数, 触发相应的IO操作, epoll 最大的特点就是将轮询改为了回调
说说线程和协程的区别
-
线程创建和销毁消耗资源, 而协程又称用户线程, 相比线程消耗资源更少
-
一个程序可以启动多线程, 但协程必须相互合作运行
-
线程只能做到并行, 协程可以做到并发
-
线程是阻塞的, 协程是非阻塞的
-
线程是抢占式的, 协程是非抢占式的
说说怎么保证线程安全
-
导致线程不安全主要有三个原因, 原子性, 可见性, 有序性
-
原子性是指一个或多个操作在CPU执行过程中被打断
-
可见性是一个线程对共享变量的修改没有被另一个对象立即看到
-
有序性是程序的执行顺序没有按照代码的编写顺序执行, 主要是因为 JIT的热缓存及优化
-
保证线程安全一般有三种方式, 由轻到重分别是原子类, volatile, 以及加锁处理
-
原子类可以简单安全的更新一个变量, 底层使用的是CAS, 在原子包中共有17个类, 可以分为4类, 分别是原子更新基本变量, 原子更新引用类型, 原子更新属性, 原子更新数组
-
使用volatile修饰变量可以保证变量的可见性, 当修改使用volatile修改的变量时, 该变量会立刻被刷新到主内存, 同时线程本地内存中的副本变量强制失效, 必须到主内存中进行读取最新值, volatile还可以使用内存屏障对代码进行隔离, 防止JIT编译器对特殊指令进行重排序
-
加锁操作一般有两种, 一是使用synchronize关键字, 使用对象头存储锁信息, 二是实现Lock接口, 使用AQS的status状态变量保存锁信息
java 基础
SimpleDateFormet是线程安全吗
-
SimpleDateFormet 不是线程安全的, 如果内部的Canlender对象引用被多个线程操作, 会产生脏读现象
-
解决办法有
-
将 SimpleDateFormet 定义为局部变量, 局部变量为线程私有
-
使用ThreadLocal将SimpleDateFormet 变成线程私有对象
-
给SimpleDateFormet 加同步锁
-
使用java8新特性中的线程安全日期类, 比如DataTimeFormetter
说一说java锁
-
在并发编程中遇到多个线程操作同一个共享变量, 同时对数据读写会造成数据不一致的现象, 就需要用到锁
-
java 的缩包括互斥锁, 读写锁, 重入锁, 公平锁, 悲观锁, 乐观锁, 自旋锁, 偏向锁, 共享锁, 排他锁(独占锁)等
-
根据线程是否锁住同一资源的情况
-
悲观锁认为读数据的时候会被人修改, 所以每次都上锁, 乐观锁认为读数据的时候不会被人修改, 不会上锁, 只有更新数据的时候通过CAS判读数据是否有修改, 修改就报错或重试
-
根据多个线程是否共享一把锁的情况
-
共享锁是指并发情况下锁可以被多个线程持有, 共享锁只能读数据, 比如ReentrantReadWriteLock就是共享锁, 排他锁只能被一个线程持有, 排他锁可以读写数据, 具体实现有synchronize, Lock
-
多线程获取锁的时候是否需要排队
-
公平锁需要按照申请锁的顺序获取锁, 非公平锁相反, synchronize就是非公平锁
-
一个线程的多个流程是否获取锁的情况
-
一个线程的多个流程可以获取同一把锁, 就是用可重入锁, 比如ReentrantLock, synchronize 反之为不可重入锁
-
如果某个线程使用说获取同步资源失败, 但有不希望线程挂起阻塞, 就可以使用自旋锁
-
自旋锁不会将线程阻塞, 而是执行循环检查, 比如AtomicInteger类
-
线程同步获取锁的状态是否发生改变, 细节流程是否改变
-
synchronize中的锁升级就是根据竞争的程度进行锁状态改变, 有①无锁②偏向锁③轻量级锁④重量级锁
请你说说JUC
-
JUC 是jdk1.5提供的并发包, 主要支持各种并发工具, 这些工具大致可以分为5类, 分别是原子类, Lock锁, 并发容器, 线程池, 并发工具
-
原子类主要用来更新一个变量, 原子包下主要有17个原子类, 可以分为原子更新基本类型, 原子更新引用类型, 原子更新属性, 原子更新数组
-
Lock 需要显示使用和释放锁, 弥补synchronize的缺陷, 包括响应中断, 超时机制, 阻塞机制, 多变量条件等
-
线程池主要是对线程创建使用进行限制, 节约资源, 保持效率
-
并发容器可以分为3类: 分别是①降低锁粒度提高并发性能, 比如concurrentHashMap, ② 使用写时复制技术提高并发性能, 比如 CopyOnWriteArrayList, ③通过Lock实现的阻塞队列, 比如ArrayBlockingQueue
-
并发工具比如易semaphore信号量可以限制同时访问特定资源的线程数量, ②CountDownLatch允许一个或多个线程等待其它线程完成, ③ Cyclibarriar通过内存屏障保证所有线程都到达才放行
https://www.nowcoder.com/exam/interview/detail?questionClassifyId=0&questionId=2412520&questionJobId=160&type=1
CountDownLatch 与 CyclicBarriar 有什么区别
-
CountDownLatch 可以理解为发令枪, 让一个或多个线程等待, 直到其它多线程执行的一组操作全部完成以后, 这些等待的线程才会继续执行
-
单等待如进程启用多线程调用远程接口, 当数据全部返回后进程才继续执行
-
多等待比如秒杀场景, 将多线程进行等待, 最后同时启动秒杀, 可以提高并行性
-
CyclicBarriar 允许一组线程相互等待, 直到所有线程都到一个公共的屏障点才一起放行
-
两者的区别有:
-
CountDownLatch 的计数器只能使用一次, CyclicBarriar 的计数器可以通过reset() 进行重置多次使用
-
CountDownLatch 会阻塞主线程, CyclicBarriar 不会阻塞主线程, 但会阻塞子线程
-
CyclicBarriar 可以处理更为复杂的业务场景, 比如①计算发生错误可以结束阻塞 ②重置计数器 ③重新执行程序
-
CyclicBarriar 的getNumberWaiting() 方法可以获取被 CyclicBarriar 阻塞的线程数量, isBroken() 方法可以判断阻塞线程是否被中断
说说内存溢出
-
内存溢出是程序运行过程中一次性申请的内存大于系统分配的内存, 就会导致内存溢出
-
内存溢出主要原因有: ①一次加载数据过多, 内存装不下 ② 程序陷入死循环 ③ 初始分配内存太小
-
解决方法包括:① 修改JVM启动参数 ② 进行错误日志排除 ③ debug ④ 检查程序代码
-
内存溢出发生位置: ① 堆 创建了大量对象 ② 方法区 创建过多动态类 ③ 栈 递归太深
说说内存泄漏
-
内存泄露是内存中的对象不再使用, 但是JVM又回收不了的内存
-
内存泄露有广义也有实际的, 实际的内存泄露有 数据库的Connection和网络IO, Socket流没有及时关闭, 还有 ThreadLocal的线程变量在线程池中没有手动清除
-
解决方式可以审查代码, 或者查看内存日志, 最重要的是借助应用程序内存分析工具, 比如jconsole, jprofiler等, 或者数据敏感区不使用强引用等
请你讲一下Java 8的新特性
-
java 8 增加了一些新特性
-
首先是lambda表达式, 他运行将功能函数作为参数传递, 并且可以简化单接口实现, 简化代码
-
其次是接口中增加default方法, 实现新的规范定义
-
然后就是方法函数, 俗称语法糖, 可以多样化方法调用
-
还有 Data Time API, 加强时间与日期的处理
-
最后就是 Stream API , 可以配合Optional的使用, 支持对流的函数式操作
说说你对反射的了解
-
反射就是在程序运行时动态获取某个类的动态类对象, 通过类对象可以获取通过这个类锁创建的实例的属性和方法
-
反射的优点就是可以增加代码的灵活性, 也是框架的核心 缺点是反射速度相比直接操作代码要慢, 且不安全
-
反射的应用场景包括通过Class.forName()获取JDBC连接对象, AOP中实现代理, Spring创建context时加载bean等
String, StringBuilder和StringBuffer的区别
-
值的可变性方面
-
String 内部的value值是用final修饰的, 每次修改value值就意味着新创建对象, StringBuilder 和 StringBuffer 的value值没有使用final修饰, 字符窜变更的时候不会产生新的对象
-
线程安全方面
-
String是不可变类, 是线程安全的, StringBuffer中的方法都用了synchronize修饰, 也是线程安全的, 只有 Stringbuilder 不是线程安全的, 单线程的情况下建议使用StringBuilder, 多线程建议StringBuffer
-
性能方面方面
-
String因为使用final修饰, 在做字符窜拼接和修改的情况下都会反复创建对象和分配内存, 所以效率最低, StringBuffer 较好,他的可变性使得字符窜可以被修改, 但他的每个方法加了synchronize, 性能略差与StringBuilder
-
数据存储方面
-
String 的数据是存储在字符串常量池中, StringBuilder和StringBuffer是存储在堆内存中, StringBuider和StringBuffer都是派生自抽象类 AbstractStringBuilder, jdk1.7的value值是char类型, jdk1.8就变成了byte
说说ArrayList和LinkedList的区别
-
ArrayList底层是数组, LinkedList底层是双向链表
-
ArrayList查询修改比较快, LinkedList插入和删除比较快
-
LinkedList比ArrayList存储更耗内存, 因为LinkedList不仅要存储数据, 还有维护两个引用
你知道哪些线程安全的集合
-
早一些的有 Vector, HashTable
-
可以使用Collections的synchronizeXXX()方法将集合包装成一个并发安全的类
-
jdk1.5后, JUC并发包下增加了很多高性能的并发类, 主要分为3类
-
第一类通过降低锁粒度提高并发性能的容器, 比如ConcurrentHashMap
-
第二类是通过写时复制技术并发容器, 比如 CopyOnWriteArrayList
-
第三类是通过Lock实现的阻塞队列, 比如ArrayBlockingQueue
说说HashMap
-
HashMap 是基于hash表对Map接口的实现类, 特点是访问速度快, 但是不保证映射顺序, 允许插入空值与空键, 还有 HashMap 本身不是线程安全的, 并发场景下可以采用ConcurrentHashMap
-
jdk1.7 的时候采用的还是数组+单链, jdk1.8就变成了数组+链表/红黑树, 使用的是链式寻址法解决hash冲突, 最大容量是 Integer.MAX_VALUE
-
put() 操作的时候发现是第一次初始化就初始化为16容量, 下标的计算是 key 的 hash 高16位与低16异或得到新 hash, (Array.length - 1) & hash
-
加载因子就是 0.75, hash桶的长度到达临界值时按两倍扩容,当链表长度达到8且数组容量到64的情况下会进行红黑树化, 链表长度减为6的时候就会进行链表退化
-
put() 操作会触发动态扩容, 可以保证 HashMap 不消耗过多内存资源的情况下, 保证有效的容量
-
关于负载因子的设计与统计学的泊松分布有关, 负载因子设置为0.75可以使链表达到8的可能性几乎为0
-
参考
https://baijiahao.baidu.com/s?id=1696795083461419054&wfr=spider&for=pc
# HashMap产生死循环问题的源码 transfer()
https://blog.csdn.net/u010010428/article/details/52042644
说说 ConcurrentHashMap
-
ConcurrentHashMap 基本延续HashMap的设计思想, 和HashMap不同的是数组分为大数组Segment和小数组HashEntry
-
ConcurrentHashMap 在jdk1.7是分段数组+链表,采用头插法会产生循环死链问题, jdk1.8是数组+链表+红黑树
-
ConcurrentHashMap 初始化容器, 头结点和查找数据不加锁, 而是通过CAS+volatile实现安全性和可见性
-
ConcurrentHashMap 插入和修改需要使用synchronize加锁, 加锁对象是头结点, 粒子是槽, 可以提高并发性能
-
ConcurrentHashMap 支持并发扩容和扩容时查找
-
ConcurrentHashMap 不允许插入空值, 是为了避免并发场景下会产生歧义问题
HashMap如何解决hash冲突
-
解决hash冲突主要有4种方式, 分别是开放地址法, 链式寻址法, 再哈希法, 建立公共溢出区
-
HashMap主要采用链式寻址法
https://blog.csdn.net/weixin_43763697/article/details/126882394
数据库
事物如何保证原子性
-
事务可以通过 undo/redog log 实现原子性, 每次写一个事务, 都会修改 bufferpool, 在将bufferpool中的值刷新到磁盘前, 先写入日志, 在进行数据提交
https://blog.csdn.net/L_IK_Y/article/details/121791171
sql优化常用的几种方法
-
做优化之前我们一般使用EXPLAIN查看SQL的执行计划
-
尽量少使用 select *, 字段多会增加不必要的开销
-
查询一条数据的时候加上 limit 1
-
排序尽量用到索引
-
模糊查询的时候不要使用 % 作前缀
-
进行常用字段与不常用字段拆分
-
联合查询可以使用中间表
https://blog.csdn.net/Lost_in_the_woods/article/details/125659716
https://blog.csdn.net/wwg1234wwg1234/article/details/123305166
MySQL的慢查询优化有了解吗
-
考虑索引没起作用的情况
-
使用模糊查询的时候 % 不要放在第一个位置, 放在开头不会使用索引, 但放在末尾会使用
-
没有匹配最左前缀
-
or关键字会引起索引的失效
-
多个索引, mysql只会使用其中一个索引
-
优化数据库结构
-
字段拆分, 将常用和不常用的字段进行拆分
-
增加中间表, 将需要联合查询的数据放到一个中间表中, 减少多表访问
https://blog.csdn.net/qq_35571554/article/details/82800463
说说InnoDB的MVCC
-
MVCC 是指多版本并发控制, 逻辑上用来维护一个数据的多个版本, 以更好的方式解决读写冲突问题
-
MVCC有三个关键数据结构:
-
隐藏列, 记录的是本行的事务id, 以及指向undo log的指针
-
版本链, 是基于 undo log , 会指向其它版本
-
版本选择 ReadView, 根据事务id与快照比较决定事务是否改变
-
MVCC 是INNODB的RP用来解决脏读, 不可重复读, 和幻读问题的
MySQL主从同步是如何实现
-
主服务器会将数据更改记录到二进制日志中
-
从服务将主服务器的二进制日志拷贝到自己的中继服务器
-
通过主服务器的日志文件将中继服务器的数据进行更新同步
说说数据库引擎有哪些,各自有什么区别
-
MySQL数据库的执行引擎有INNODB, MYISAM, Memory, Archive, 我们主要使用的是INNODB, MYISAM
-
INNODB 支持事务, 支持外键, 使用行锁, 不支持全文索引, 有内存限制(64TB),完整性较高
-
MYISAM 使用表锁 不支持事务, 支持全文索引, 没有内存限制, 不支持外键, 查询和插入效率高
-
Memory 支持表锁, 是将数据存储在内存中, 一旦停止服务就会丢失数据, 用来存不太重要的数据
-
Archive 支持行锁, 用来存经常不用的数据
数据库为什么不用红黑树而用B+树
-
红黑树是一颗近似平衡二叉树, 在数据量庞大时树很高, 树的高度决定我们需要IO的次数, 磁盘IO会导致效率降低
-
B+数时一颗平衡多叉查找树, B+树高度一般是2-4层, 所有的记录都是按键值大小顺序存放在同一层的叶子节点上, 在叶子节点间还建立了连接, 能保证范围查询时找到起点和终点后快速取出数据
https://blog.csdn.net/qq_24313635/article/details/102924190
说说聚簇索引和非聚簇索引
-
聚族索引是数据与索引一起存储, 非聚族索引是数据与索引分开存储
-
聚族索引在物理上是连续的, 非聚族索引是逻辑上连续, 聚族索引只能有一个, 非聚族索引可以有多个
-
聚族索引的叶子节点存储的是数据, 非聚族索引叶子节点存储的是指向数据的引用
-
INNODB数据库中一般会有一个根据主键建立的聚族索引, 没有主键也会根据隐藏的列生成特殊的聚族索引, 还有辅助索引, 辅助索引中存键值, 通过辅助索引找到键值还要在聚族索引查找才能得到数据
-
MYISAM中一般是非聚族索引
说说MySQL索引,以及它们的好处和坏处
-
索引本质上是通过预编译+树型结构加快检索效率, 我们建立索引就是是为了提高数据的检索速度, 索引可以认为是指向表行的指针, 它的底层就是B+树
-
合理利用索引, 不仅可以提高检索能力, 还可以加快分组排序与分组速度
-
索引的类型包括主键索引、外键索引、普通索引、唯一索引、混合索引、全文索引, 需要注意的是INNODB数据库引擎默认不支持全文索引, 需要通过第三方工具
-
建立索引需要消耗额外的存储空间, 如果对建立索引的列进行增删改就必须重新维护索引树, 维护会造成额外开销, 并且如果检索不符合“最左前缀”的原则, 就不会命中索引
说MySQL的事务隔离级别
-
MySQL的事务隔离级别有4种, 分别是读未提交, 读已提交, 可重复读, 序列化, 主要用来应对脏读, 不可重复读, 幻读问题
-
读未提交没有加锁, 性能最好, 最不安全, 会产生3中问题
-
序列化有读共享锁和写排他锁, 安全度最高, 会解决三种问题
-
读已提交可以避免脏读问题
-
可重复读可以解决脏读, 不可重复读问题, 可以使用间隙锁解决幻读问题
Spring Boot
如何解决跨域问题
-
同源策略是跨域问题产生的原因, 同源策略是指用户输入URL中包含的协议,域名, 端口必须一致, 同源策略限制只有游览器有
-
解决办法我知道的有两种:
-
可以利用游览器的预检机制, 在后端服务器添加Cors(Cross origin resources sharing)的策略配置, 在springboot项目中只需要在方法上添加@CrossOrigin 注解即可, 或者实现WebMvcConfigurer 接口重写addCorsMappering()方法实现跨域支持
-
在Vue项目中使用项目的代理服务器, 服务器与服务器通信不存在跨域问题
https://www.jianshu.com/p/1d3f4f249a85
说说Spring Boot的自动装配
-
SpringBoot 主要基于注解编程和约定大于配置规则设计的, 自动把其它组件中的 bean 装载到IOC容器中, Spring Boot 的自动装配需要引用场景启动器
-
springboot的自动装配核心就是EnableAutoConfiguration注解, 他会开启springboot的自动装配功能
-
在springboot项目创建的时候, 会默认创建一个用springbootApplication注解标注的类, 就是主配置类
-
springboot是一个复合注解, 里面主要有三个注解, 分别是SpringbootConfiguration, 他是标注当前类是springboot的配置类, ComponentScan 是用来排除一些过滤类的
-
最核心的就是EnableAutoConfiguration注解, 他也是一个复合注解
-
其中有AutoConfigurationPackage注解, 用来指定组件的扫描规则, 还有 import注解, 会引入一个AutoConfigurationImportSelector, 这个类中的方法selectImports()会扫描META-INF包下所有注册的可用的类, 将其全限定类名放入字符窜数组返回并加载到IOC容器中
-
这些注解标注了资源的配置方式, 解析还要看run()方法中的prepareContext()和refreshContext()方法调用解析注解才行
https://blog.csdn.net/qq_57434877/article/details/123933529
https://blog.csdn.net/m0_46316970/article/details/125898849
说说Spring Boot的启动流程
-
SpringBoot 启动主要包括两个部分, ① 完成springApplication的构造② 调用对象run()方法
-
在创建SpringApplication中, 在其构造方法中主要完成一些参数的初始化, 最重要的是判断当前应用程序的类型, 设置初始化器, 设置监听器, 并加载spring.factories到缓存中, 为自动装配做准备
-
在run方法中主要完成几个步骤
-
首先会创建stopwatch计时器记录项目启动时间
-
创建SpringApplicationListeners并进行run方法监听
-
初始化Arguments
-
加载配置环境Environment并添加进监听器中
-
打印banner信息
-
创建ConfigurableApplicationContext对象, 执行prepareContext和refreshContext方法
-
prepareContext是将Arguments, Environments, listeners和banner与Context对象绑定
-
refreshContext是是自动配置的关键, 包括加载spring.factories和bean的实例化
https://blog.csdn.net/qq_57434877/article/details/123933529
https://blog.csdn.net/zsh2050/article/details/124514882
https://blog.csdn.net/weixin_49023067/article/details/125429261
说说Spring Boot的起步依赖
-
Spring Boot的起步依赖又叫场景启动器, 通过依赖传递与约定大于配置的原则, 封装某一个场景开发中需要的全部依赖, 比配置自动配置类, 使SpringBoot可以在启动时自动加载自动装配配, 完成相关配置
Spring
Spring 事务的传播行为有哪些
-
spring 事务的传播行为是多个声明了事务的方法相互调用的时候事务的使用规则
Spring 中用到了那些设计模式
-
工厂模式, beanfactory就用到了简单工厂模式
-
单例模式, bean 的作用域默认是singleton
-
装饰器模式, 都是以XXXWrapper命名, 比如beanWrapper
-
策略模式, bean的实例化就是策略模式, 因为它包含原生和代理实例化, 不同对象实例化逻辑也不同
-
适配器模式, MVC 模块中HandlerAdapter就是
-
代理模式, AOP 模块中的AOPProxy
-
模板方法模式, 主要用来解决代码重发问题, 都是XXXTemplate命名, 比如jdbcTemplate
-
观察者模式, 比如spring的监听器ApplicationListener
导致spring事务失效的原因有哪些
-
进行事务的方法不是public方法
-
进行事务的类没有被spring托管
-
不正确的异常捕获
-
抛出的异常不是运行时异常
-
同一类中的方法调用, 比如一个类中一个没有事务的方法A调用有事务的方法B, 会导致事务失效
-
propagation的传播行为配置错误
-
rollbackFor 参数设置错误
-
没有配置spring的事务管理器
-
所使用的数据库本事不支持事务
谈谈你对 SpringMVC 九大组件的理解
-
SpringMVC 九大组件有
-
MutiparResolver 多文件上传组件
-
localeResolver 多语言支持组件
-
ThemeResolver 主题模板处理组件
-
HandlerMapping URL映射组件
-
HandlerAdapter 业务逻辑适配组件
-
handlerExceptionResolver 异常处理组件
-
RequestToViewNameTranslator 实体名称提取组件
-
ViewResolver 视图渲染组件
-
FlashMapManager 内存管理组件
介绍一下Spring MVC的执行流程
-
首先http请求会到达我们配置的统一前端控制器 DispatchServlet, DispatchServlet 会对URL进行解析得到URI
-
DispatchServlet 会请求请求映射器 handlerMappering 进行处理, handlerMappering 会根据URI寻找处理器 handler 以及路径上的过滤器, 串成一条执行链返回给 DispatchServlet, 如果根据URI找不到Handler, 查看是否开启静态资源处理, 找得到静态资源就返回, 找不到就报404
-
DispatchServlet 会请求处理映射器 HandlerAdapter 处理请求链上的信息 会根据 request 中的数据模型进行handler入参, 执行 Controller, 返回一个ModelAndView对象给DispatchServlet, 在入参过程中会进行一些数据转换和数据验证
-
DispatchServlet 会请求视图解析器 ViewResolver 进行视图解析, 找到对应物理视图返回
-
DispatchServlet 最后会根据 ModelAndView 中的数据对视图进行渲染, 返回视图给前端
-
上述过程是没有拦截器的时候, 如果配置了拦截器, 在调用handlerAdapter执行handler方法之前, 会正向执行prehandler方法, handler方法执行完毕后会逆向执行afterhandler方法, 视图渲染完成返回之前会逆向执行aftercompletion方法
说说你对MVC的理解
-
MVC是一种设计模式, 在该模式下MVC被分为三层, 包括 Model 数据模型, 封装数据以及对数据的操作, View视图模型, 用于展示模型数据, Controller 控制器, 数据的处理逻辑, 连接View与Model的桥梁
-
常见的MVC为JavaBead+Jsp+Servlet, JavaBean封装业务数据并提供相关的业务处理, View用于展示数据并发送请求, Servlet 用户接收请求, 将前端数据转为模型数据, 并调用业务操作进行数据更新, 返回展示
-
现在JavaBean+Jsp+Servlet已经过时, 常用地是SpringMVC, 他是基于java的MVC模型轻量级框架, 主要包括DispatchServlet, HandlerMapping, HandlerAdapter, ViewResolver
动态代理与静态代理有什么区别
-
AOP 有SpringAOP为代表的的动态代理, 以及以AspectJ为代表的静态代理
-
AspectJ 是在前端编译阶段将通知织入到被代理类的字节码文件中, 运行时生成增强后的代理对象
-
SpringAOP是在程序运行时通过方式在内存中临时生成一个AOP对象, 在指定切点织入增强
简略
https://blog.csdn.net/qq_44761854/article/details/123508306
详细
https://www.cnblogs.com/chaoesha/p/13037368.html
说说你对 IOC 的理解
-
IOC 即控制反转, 是面向对象的编程思想, 把对象的创建, 初始化, 销毁交给Spring进行管理
-
IOC 思想是基于IOC容器实现的, 底层基于XML解析, 工厂模式, 放射实现
-
IOC的实现方式是DI, 依赖注入方式包括set注入, 构造器注入, 还有已经淘汰的接口注入, 因为接口注入的侵入性太强
-
IOC 的两种实现方式分别是beanFactory和ApplicationContext, IOC 对bean的两种管理方式分别是xml <bean><property>和注解 @Component
说说你对AOP的理解
-
AOP是一种面向切面的编程思想, 将程序抽象成各个切面, 将其中公共部分提取并进行重用, 可以减少代码重复率与降低耦合度
-
AOP的作用是通过预编译和运行期动态代理的方式在不修改源代码的情况下给程序动态的增加功能
-
AOP有两种代理方式, 一种是jdk动态代理, 创建的是接口代理子类, 另一种是CGLib动态代理, 创建子类的代理实例, AOP不能代理final修饰的类
-
我们可以使用AOP实现日志和事务功能
说说 Bean 的生命周期
-
Bean 的生命周期大致可以分为bean的定义, bean 的初始化, bean 的使用, bean 的销毁
-
在实现 BeanPostProcessor接口的情况下可以分为7小部分:
-
bean构造器实例化
-
set方法注入属性
-
postProcessBeforeInitailization 初始化前置
-
initMethod初始化方法
-
postProcessAfterInitailization 初始化后置方法
-
使用
-
显示调用父类的close进行销毁
Spring中的bean是线程安全的吗
-
Spring中的bean是根据我们定义的类托管我们的实例, 所以bean是否安全与与容器本身无关
-
bean是有作用域的, 当bean的作用域为Singleton的有状态bean时会有安全问题, 有状态是指多线程情况下会对bean的成员变量进行更新操作
-
我们可以通过将scope修改为prototype或者将变量存进ThreadLocal中保证线程安全
说说你对SpringBean的理解
-
在Spring中, 构成应用程序的主干并由SpringIOC容器管理的对象称为bean
-
SpringBean的定义有三种方式, ①基于xml文件配置 ②基于注解扫描的方式配置 ③基于java类配置
-
SpringBean的创建策略是将解析好的配置内容转化为beanDefinition对象, 以beanName为key, beanDefinition为value存到一个Map中, 实例化要根据具体的创建策略实例化对象
Redis
说说Redis的缓存淘汰策略
-
Redis淘汰删除过期key分两种, 一种是惰性删除, 一种是定期删除
-
惰性删除是在用户访问某个key的时候检查该key是否过期, 过期就删除
-
定期删除是将设置过期时间的key放进字典中, 按照简单贪心策略定期10s扫描, 过期就删, 当过期key超过25%, 重复扫描
-
当写入key超过maxmemory时, 按照maxmemory-proxy指定策略删除, 策略包括两种, 分别是LRU和 LFU
说说Redis的主从同步机制
-
Redis主从同步分为全量同步与增量同步
-
全量同步主要发生在从机第一次连接主机, 发生 ‘psync’ 命令请求与主机同步, 主机发现并没有从机id, 于是会将内存中的数据备份到RDB文件中发送给从机, 从机将RDB文件中的数据拷贝到本地存储, 形成全量同步
-
之后请求中, 通过 ‘psync’ 携带主机id和偏移量, 主机就从偏移量开始复制数据给从机进行数据同步, 而不是复制全部, 完成增量同步
如何实现Redis高可用
-
Redis高可用主要体现在三个个方面, 分别是主从, 哨兵和集群
-
主从实现的数据的读写分离, 使服务器可以应对更高的QPS, 而且从服务器还可以同步主服务器的数据,防止数据丢失
-
哨兵模式可以创建多个哨兵节点, 每个哨兵节点会监视其它数据节点和哨兵节点, 当一个哨兵发现某一个主节点不可用的时候, 会对该节点进行下线标识, 同时与其他哨兵节点进行协商, 避免误判, 当大多数哨兵都认为该几点不可用, 就推选一个从节点晋升为主节点, 保证主从, 整个工程是自动完成, 实现高可用
-
Redis集群采用虚拟槽分区实现数据分片, 他把所有键值对映射到16383个槽中(计算公式为slot=CRC16(key)&16383
), 每个节点负责维护一段连续槽位和其所映射的数据, 集群解耦了数据与节点之间的关系, 简化了槽的扩张与收缩, 也实现了高可用
说说Redis的单线程架构
-
Redis是单线程+IO多路复用模型, 但并不完全是单线程, 在网络IO与键值对的读写中时是单线程操作, 但是在持久化与异步删除的时候需要依赖其他线程完成
-
对服务端程序而言, 线程间切换需要消耗资源, Redis可以使用单线程避免
-
Redis对数据的操作是在内存中, 可以提高数据交换的效率
-
Redis采用IO多路复用模型是在单线程下也可监听多个文件描述符, 处理多个IO事件
如何利用Redis实现一个分布式锁
-
setnx + expire
-
set nx ex
-
setnx + value=系统时间+过期时间
-
使用 lua 脚本将 setnx + expire原子化
-
使用开源框架 Redission
-
多机实现分布式锁 RedLock
说说Redis的数据类型
-
Redis数据类型包括 string, list, hash, set, zset
-
string类型支持数字, 字符窜, 二进制, 编码类型为int, raw, embstr, 最多可以存储2M的数据, 主要用于存储验证码和计数
-
list 类型编码类型为ziplist, linkedlist, quicklist, 当元素个数为2, 且元素长度小于64字节使用ziplist, 不符合就使用linkedlist, quicklist是3.2之后两者的替代, 最多可以存储2^32-1个元素, list 可以用来做秒杀缓存
-
hash 编码类型为ziplist和hashtable, 当不满足ziplist条件时就使用字典存储, 最多可以存储2^32-1个元素, 主要用来存储对象
-
set编码类型为intset和hashtable, intset底层为整数集合, 代表无序不重复列表, 可以存储2^32-1个元素, 可以求交并补集,主要用来搞抽奖活动
-
zset编码类型为ziplist和skiplist, 当存储元素小于128且元素长度小于64字节就用ziplist, 不然就是skiplist提升速度, 最多可以存储2^32-1个元素, 常用来做排行表
-
还有新数据类型 bitmaps用于位运算, hyperloglog用于求基数, 以及Geospatial用于经纬度查询
-
最后就是 redis6 的Streams, 是一个支持多播, 支持持久化的消息队列
说说乐观锁和悲观锁
-
乐观锁就是总是假设最好的情况, 每次去拿数据都认为没人会修改数据, 因此不会上锁, 悲观锁就假设最坏的情况, 每次去拿都认为别人会修改数据, 每次都要上锁
-
乐观锁通过版本号和CAS实现, 悲观锁通过持有对象锁实现
-
乐观锁使用与多读, 提高吞吐量, 悲观锁用于多线程操作敏感数据, 保证安全
-
乐观锁应用场景有版本控制git和svn, 悲观锁应用要数据库的行锁, 表锁, 读锁, 写锁
说说Redis的持久化策略
-
Redis持久化策略包括三种, 分别是RDB, AOF, AOF+RDB
-
RDB是默认开启的, 通过快照方式将数据以二进制流的方式写入dump.rdb文件中, AOF 需要手动开启, 他通过独立日志的方式追加写命令到协议文本中
-
RDB恢复文件要比AOP快, RDB 是存储与恢复快, 但是事故窗口大, AOP是事故窗口小, 但恢复数据慢, 可以采用 AOP+RDB的方式, 在AOF文件中追加RDB命令, 减小事故窗口的同时提高恢复速度
-
在两者都存在的情况下, 优先级 AOF > RDB
说说缓存穿透、击穿、雪崩的区别
-
缓存穿透是连续查找缓存和数据库中都不存在的数据, 透过缓存直接访问数据库, 解决办法有缓存null值, 设置访问白名单, 实时监控, 使用布隆过滤器
-
缓存击穿是当某一key过期时却有大量请求访问, 就会穿过缓存直击数据库, 解决办法就是提前缓存热点数据, 使用分布式锁对访问数据库的请求进行限制
-
缓存雪崩就是大量请求访问时大片key集中过期, 大量请求访问数据库造成数据库崩溃, 解决办法主要是将同时过期的key打散, 也可以使用分布式锁
设计模式
说一说设计模式
-
设计模式总共有23种, 可以分为3大类
-
第一类是创建型, 包括单例, 原型, 工厂方法, 抽象工程, 建造者
-
第二类是结构型, 包括适配器, 代理, 装饰, 桥接, 组合, 外观, 享元
-
第三类是行为型, 包括责任链, 观察者, 模板方法, 命令, 访问者, 迭代器, 中介者, 备忘录, 解释器, 状态, 策略
说说单例模式
-
单例模式是构造器私有, 他可以保证上下文中只有一个实例, 避免对象重复创建销毁增加开销, 同时避免大对象占资源
-
单例模式有饿汉式, 懒汉式, 双重检查, 静态内部类, 和枚举
DCL 的单例模式中, 为什么要做两次检查
-
第一次检查是为了保证只有在第一次并发的情况下才会阻塞, 提高性能
-
第二次检查是为了保证单例, 避免多线程同时进入第一次检查, 造成重复创建单例对象
-
加分: 在并发情况下, new 一个对象可能会发生指令重排, 我们可以给声明的单例 + volatile关键字
讲讲工厂模式
-
工厂模式就是定义一个创建产品对象的接口, 而具体实现推迟到子类进行
-
具体实现思路就是根据传入参数的不同返回不同的实例对象, 且这些实例都有一个共同父类或接口
-
工程模式实现有简单工厂, 工厂方法, 抽象工厂
JVM
什么时候引起full gc,有什么危害
-
GC触发机制包括MinorGC 针对年轻代, MajorGC 针对老年代, FullGC针对混合区
-
引起Full GC的情况包括①调用了System.gc②老年代空间不足③方法区空间不足④大对象直接进入老年代, 但老年代可用空间不足
-
FullGC会触发STW, 频繁的FullGC会造成卡顿, 用户体验度下降
https://blog.csdn.net/weixin_45525272/article/details/126370223
类的实例化过程
-
类的实例化过程包括 ①类加载 ② 在堆中划分内存 ③ 属性赋0值 ④ 对象状态设置 ⑤ 执行构造器
-
类加载就是去方法区中检查是否存在该类信息, 存在就下一步, 不存在先进行类加载
-
内存划分就是根据类信息确定类的大小, 在堆中划分出一块内存给对象创建
-
属性赋0值可以保证实例变量在不赋初值的情况下有值
-
对象状态的设置包括类元信息和对象分代年龄划分等
-
之后就是执行类构造器
介绍一下分代回收机制
-
分带回收机制是基于不同对象的生命周期不同, 可以对不同生命周期的对象采用不同的垃圾回收算法, 可以提高回收的效率
-
年轻代存放的是朝生夕死的对象, 一般对应的minor GC和复制算法
-
老年代的一般是生命周期比较长的对象, 对应的major GC和标记整理算法
-
Full GC 是清除整堆空间, 有 老年代满了, 大对象直接进入老年代等情况
https://baijiahao.baidu.com/s?id=1680869052262590767&wfr=spider&for=pc
https://blog.csdn.net/iris_csdn/article/details/115674242
https://www.cnblogs.com/huaxiansheng/p/15310520.html
说说GC的可达性分析
-
GC的可达性分析需要用到可达性算法, 可达性算法主要用来判断一个对象是否存活
-
算法的基本思想是 从 GC Root出发, 途径引用链进行遍历标记, 可以到达的对象称为可达对象, 反之为不可达对象
-
GC Root 的对象一般包括虚拟机栈中直接引用的对象, 本地方法栈中直接引用的对象, 方法区中引用的对象, 同步锁引用的对象等
-
一个对象的存活需要经过两次标记, 两次标记之间对象可以被救活
说说垃圾收集器
-
JVM发展历程中共包含7中垃圾回收器
-
首先是Serial GC, 是第一款串行垃圾回收器, 主要回收新生代, 与之配合使用的是serial Old GC, 负责的是老年代垃圾回收
-
后面出现了第一款并行垃圾回收器ParNew, 负责新生代回收, 与之对应的是CMS, 是负责老年代回收, CMS还主打低延迟效果
-
接着是JDK8的默认回收器 Parallel Scavenge, 负责年轻代回收, Parallel Old 负责的是老年代回收, Parallel 系列回收器主打高吞吐量
-
后面出现了混合回收器, 也就是jdk9默认的回收器Garbage first, G1可以回收新生代和老年代, 还提出了内存分区region, 更有利于内存回收
说说Java的四种引用方式
-
java有四种引用方式, 分别是强引用, 弱引用, 软引用, 虚引用
-
强引用, 就是new关键字创建的对象返回的引用, 只要不主动置空, 该引用指向的对象不会被JVM进行GC, 同时强引用也是内存泄露的主要原因
-
软引用, 主要通过SoftReference进行创建, 可以内存充足下存活, 内存紧张时会被回收
-
弱引用, 主要通过WeakReference创建, 可以撑到下一次GC之前
-
虚引用, 对对象影响不大, 在被GC的之后会通过同步队列返回一个系统的通知
说说JVM的垃圾回收算法
-
JVM的垃圾回收算法一共有三种, 分别是标记清除算法, 标记复制算法, 标记整理算法
-
标记清除算法的思路就是从 GC Root 出发, 一次遍历标记所有不可达对象, 二次遍历清除所有被标注的对象, 也可以反过来, 标记清除算法会产生两个问题: ① 效率不稳定, 标记与清除的执行效率取决于存活对象的数量 ② 会产生碎片化问题, 当需要创建大对象需要的内存足够的时候却存不下, 还需要额外的空间维护空闲列表
-
标记复制算法是将内存空间对半分, 一般使用, 一般空闲, 但需要GC时, 将存活的对象复制到另一半中, 就可以直接清除已经使用的那一半了, 标记复制会造成空间浪费严重, 而且复制对象还要维护对象的引用, 主要针对年轻代
-
标记整理在标记阶段和标记清除类似, 只是不是直接清除, 而是将对象进行边界移动, 俗称整理, 在堆边界外的垃圾进行GC, 可以空出连续空间, 方便指针碰撞, 缺点就是也需要对引用对象的地方进行维护, 主要针对老年代
说说你了解的JVM内存模型
-
jvm内存模型主要包括类加载器, 运行时数据区, 执行引擎, 本地本方法库, 本地方法接口
-
执行引擎包括解释器和JIT即时编译器, 负责执行被载入类的方法中的指令
-
类加载器包括加载, 连接, 初始化阶段, 将字节码文件加载进内存
-
运行时数据区用来存储数据, 包括对象信息, 局部变量, 中间结果等
说说JVM的垃圾回收机制
-
jvm 垃圾回收机制主要判断哪些是垃圾,什么时候回收, 怎么回收
-
判断对象是否是垃圾就是判断对象是否可达, 主要有两种算法, 一是引用计数算法, 二是可达性分析算法
-
当对象不可达的时候, 且进行GC的时候就会进行垃圾回收
-
回收主要通过分代假说理论, 使用不同的垃圾回收算法, 调用相应的垃圾回收器进行回收
说一说JVM内存区域
-
JVM的内存模型也就是JVM模型中的运行时数据区, 主要包括①本地方法栈②PC寄存器③本地方法栈④堆⑤方法区⑥CacheCode(JIT 编译产物)
-
本地方法栈主要用于对本地方法的管理和调用, 本地方法一般就是C编写的
-
PC 寄存器主要存储下一条执行指令的地址, 包括分支循环异常处理等, 需要注意的是, 如果执行的是本地方法, 则PC寄存器的值为undefined
-
虚拟机栈中存放的就是栈帧, 一个栈帧的入栈意味着一个方法的调用, 出栈意味着方法的结束, 栈帧中只要有5种结构: ①局部变量表 ②操作数栈 ③ 动态链接 ④方法返回地址 ⑤一些附加变量
-
局部变量表主要存储的是方法参数和局部变量
-
操作数栈存储计算的中间产物, 做临时存储
-
动态链接存储常量池中栈帧对应方法的引用
-
方法返回地址就是下一指令地址
-
堆和方法区是我们关注的重点, 只有这两个地方才可以进行垃圾回收, 堆中一般是对象实例, 方法区包括类元数据和方法元数据以及常量池, 需要注意的是在jdk1.7的时候静态变量与字符窜常量移到了堆区, 而jdk1.8的时候元空间由本地内存接管, 堆内存采用分区管理, 分为新生代与老年代
说说类加载机制
-
类加载机制包括加载, 链接, 初始化
-
加载就是通过类的全类名获取类的二进制字节流, 通过字节流将字节码文件中静态的类存储结构转变为方法区中运行时数据结构
-
链接包括3个阶段, 分别是校验, 准备, 解析
-
校验阶段是对文件格式, 元数据, 字节码, 符号引用进行校验
-
准备阶段就是对类对象进行默认赋值, 但不包括final类变量和实例变量
-
解析阶段是将符号引用转为直接引用
-
初始化阶段会对类变量进行赋初值, 合并类成员, 执行 <clinit> 方法
说说JVM的垃圾回收算法
-
JVM 垃圾回收算法主要有三种, 分别是标记清除算法, 复制算法, 标记整理算法
-
标记清除算法会从GC Root 出发, 沿引用链对链路上的不可达对象进行标记, 这是第一次遍历, 进行标记. 然后重新遍历, 将被标记的对象进行清除, 或者将其标记为可用空间, 标记清除容易产生两个问题: ① 效率不稳定, 标记与清除的效率取决于存活对象的数量 ② 容易产生碎片化问题, 导致大对象无法存储直接进入老年区, 以及维护额外空间的空闲列表
-
复制算法就是将内存空间分半处理, 一般使用, 一般空闲, 当一半满了的时候, 将存活对象复制到另一半, 直接清除这一半即可好处是简单不会有碎片问题, 坏处就是十分浪费内存空间, 当存活的对象较多的时候, GC 的时长也会变长
-
标记整理算法的标记阶段和标记清除相似, 但不是标记完了直接清除, 而是整理, 将存活对象进行边界移动, 对边界外的不可达对象进行回收, 优点也是不会产生碎片化问题, 但是要对引用存活对象的地方进行重新标记的维护, 也是额外开销
-
针对不同存活的对象, 使用不同的垃圾回收算法可以明显的提高效率, 所以JVM的内存是分带管理的, 可以分为年轻代和老年代和永久代, 复制算法一般用在年轻代, 整理算法一般用在老年代, 永久代的对象最好不要过多, 不然会出现内存溢出, 经典场景就是JSP页面过多导致内存溢出
请你讲下G1垃圾回收器
-
G1是多线程混合区垃圾回收器, 他将堆内存分为2048个Region, 可以分别代表eden, survivor, old 区大对象还可以放啊humongous中
-
垃圾回收主要回收region, 可以通过优先级表进行region回收选择, 通过 remember set记录被引用位置, 避免全堆扫描
-
G1 回收过程为 初始标记, 并发标记, 最终标记, 并发筛选回收
请你讲下CMS垃圾回收器
-
CMS 是第一款并发低延时的垃圾回收器, 他运行垃圾回收线程与用户线程同时工作
-
CMS 的回收过程为
-
初始标记, 只会标记GC Root能直接关联的对象
-
并发标记, 会和用户线程一起运行, 标记可达对象
-
重新标记, 会修正并发标记阶段因用户进程运行导致标记变动的部分
-
并发清除会清理被标记对象
-
虽然CMS达到并发低延时的效果, 但是还是有三个缺点
-
并发垃圾线程还是占用了一部分用户线程, 降低了吞吐量
-
CMS无法处理浮动垃圾, 当垃圾清理赶不上垃圾制造还是会进行Full GC
-
CMS是基于标记清除算法实现的, 会产生碎片化问题
说说JVM的双亲委派模型
-
JVM 双亲委派模型中包括引导类加载器, 扩展类加载器, 系统类加载器, 用户自定义加载器
-
JVM 双亲委派机制是指一个类加载器在收到类加载请求的时候不会先自己尝试加载, 他会向上委托, 中间各层子加载器都是如此, 当到达顶级加载器的时候, 如果该父类加载器可以加载, 他就将类进行加载, 不可以就重新向下委托
-
JVM双亲委派机制有两点好处:
-
可以防止类的重复加载
-
可以保护核心API被恶意修改
MQ
谈谈你对消息中间件的理解
-
MQ(Message Queue) 就是消息队列, 主要作为分布式应用之间实现异步通信的方式, 主要有三部分构成, 分别是 ① 生产者 ② 消息服务端, 消息服务端是核心 ③ 消费者
-
MQ主要应用场景有 ① 流量削峰 ② 应用解耦 ③ 异步处理
-
流量削峰是指流量入口过大, 同时要求服务器短时间内响应, 而性能又跟不上导致大量消息堆积造成的客户端超时等待现象(QPS), 我们可以先把请求发给MQ, 由QM平稳的分发给各个服务器.
-
应用解耦是指将一些相关的, 但是耦合度不高的应用系统关联起来, 可以解决不同应用系统使用不同的框架或者不同的编程语言造成的兼容性问题
-
异步处理主要应用于实时性要求不高的场景, 比如说登录验证码或者支付成功的通知等
测试
说一说接口测试
-
接口测试是测试组件间接口的一种测试, 测试内容主要是检查接口参数传递的正确性, 接口功能实现的正确性, 输出结果的正确性, 以及异常处理的完整性
https://www.xiaohongshu.com/discovery/item/63199e920000000011038f90
https://blog.csdn.net/waitingwww/article/details/122483131
说一说压力测试
-
压力测试是一种基本的质量保证行为, 是在计算机数量较少或资源匮乏的情况下进行
-
主要测试包括内存资源, CPU可用情况, 磁盘空间和网络宽带, 还有并发测试
-
压力测试也称强度测试、负载测试, 长时间或超大负荷地运行测试软件
https://blog.csdn.net/zhuoyue123_/article/details/123679485
https://blog.csdn.net/zhuoyue123_/article/details/123679485
Nginx
谈谈你对Nginx的认识
-
Nginx也是一个服务器, 只不过他可以做的事更多, 比如说代理服务, 负载均衡等, 并且效率比较高
-
还可以缓存静态资源, 实现动静分离, 以及URL Rewrite等, 还可以实现防盗链
https://zhuanlan.zhihu.com/p/34943332
谈谈你对负载均衡的认识
-
负载均衡是在集群模式下, 负载均衡器将任务平均分配到多台服务器上, 是请求分散, 降低服务器的访问压力
-
他主要有三种方案: ① 基于DNS 实现 ② 基于硬件实现 ③ 基于软件实现
https://zhuanlan.zhihu.com/p/553675396
Vue
Vue 底层实现原理
-
Vue 底层通过发布订阅模式结合数据劫持实现, 主要通过 Object.defineProperty() 对属性添加getter和setter方法
-
Object.defineProperty() 会有失效情况, 比如手动添加数组元素的时候, 不会为新添加的元素添加数据劫持
谈谈你对Vue声明周期的理解
-
Vue2 的生命周期图
-
Vue3 的生命周期图
其它
java与JavaScript有什么不同
-
产生背景不同, java是sun公司开发的, JavaScript是网景公司开发的
-
对象设计不同, java是面向对象语言, JavaScript是脚本语言, 内置丰富对象
-
运行机制不同, java执行之前必须编译, JavaScript由游览器解释执行
-
变量定义不同, java是强类型语言, 是动态语言, java是弱类型语言, 是静态的
说一个自己熟悉的项目,讲讲收获
网上购票系统
项目描述
-
主要流程就是模仿一下12306, 可以进行车票信息查询, 比如说站点信息, 出发日期, 价格, 余票, 下单, 支付或删除订单等
-
项目是分工合作开发, 我主要负责购票流程和订单支付
-
借鉴12306的购票流程, 我负责票的分页显示, 根据到达时间, 价格升降序, 票余量查询, 下单等流程, 自主设计用户下单但未及时付款的时候提示, 并自动删除订单. 还考虑了并发情况下超卖的问题, 使用spring事务进行回滚
-
自动消息提醒原理是利用了RabbitMQ的延时队列, 以及Redis缓存订单并设置过时时间, 在客户下单的时候将订单信息存入Redis中, 同时发送消息到延时队列, 一个8分钟的支付提醒, 一个10分钟的删除提醒, 使用 Redis缓存订单可以减少Mysql的压力, 同时提高访问效率
-
我还在支付环节设置了事务, 当多并发请求到来的时候, 会先获取数据库中票的余量, 不足就手动抛出运行时异常, 同时回退事务, 我还尝试使用mybatisPlus 的版本控制加乐观锁尝试代替事务, 但会参数剩余票过多的现象, 被我抛弃了
-
我本来打算想使用 Nginx + MQ 实现消息排队实现流量削峰的效果, 但我同学没有学习到相关知识, 我就没有实现, 我最后自己写了一个简单的登录页面, 引入 SpringSecurity 做了数据库身份验证和权限绑定, 注销等
-
期间我用 Postman测试了接口, 用户cookie进行了下单测试安全等, 用来JMeter测试了并发请求超卖问题
遇到的问题
-
我自己没有参考别人的方案自己设计一个前后分离的项目, 自己分析业务, 设计流程, 编写程序, 自己设计测试功能等, 方案比较幼稚, 流程也比较简单, 遇到的问题只能自己debug等
-
第一次尝试分模块开发, 尝试站在架构的方面考虑效率, 并发压力, 使用缓存缓解数据库压力等
-
首先是刚开始的责任划分, 我主动承担购票的流程开发也设计, 我和他一起讨论数据表, 考虑引用, 索引等
-
其次在发送延时消息的时候, 如何与前端进行异步通信, 我就采用了websocket, 解决跨域的问题我使用的是Vue的代理服务器, 没有在服务器设置CORS
-
还有就是Vue项目中使用的是UI组件搭建前端页面, 组件间会有不兼容现象, 我就自己写前端代码耗的时间就挺长一点
获得的收获
-
自己第一次尝试前后端分离项目, 第一次考虑跨域, 异步消息, 自主设计项目, 有一定的劳动成就
-
自己尝试和伙伴分模块开发, 自己设计自己项目流程图, 尝试整合自己的技术栈, 提高自己的动手能力等
场景分析
CPU 飙升, 系统反应慢, 怎么排查
-
CPU 飙高的原因可以有两个 ① CPU上下文切换过多 ② CPU资源过渡消耗
-
CPU 上下文切换过多可以是创建线程过多, 使CPU在不同线程中进行切换, 线程上下文切换会保存运行线程的状态, 让等待中的线程执行, 多切换可能会导致CPU无法执行用户指令
-
CPU 资源过度消耗可能是某个线程一直占用CPU资源, 导致其他线程无法获取CPU调度, 比如死循环等
-
解决方式可以是使用top命令查看CPU使用情况, 通过Shift+H找到消耗过高的线程, 可能会有两种情况
-
CPU占用一直是同一个线程
-
CPU利用过高的ID不断变化
-
我们可以通过 jstack 获取线程 Dump 日志, 分析日志找到问题代码