Java技术栈-尊享版(公开)
2020-06-04 14:27:10 17 举报
AI智能生成
JAVA体系知识结构:包含JAVA基础,JVM,数据库,缓存,中间件 等;java技术栈
作者其他创作
大纲/内容
19.构建工具
maven
Gradle
18.代码管理
git
svn
17.大数据
HBase
Hive
Hadoop(HDFS、MapReduce)
Storm
16.容器
Tomcat
Jetty
Nginx
K8S+Docker
15.Linux
sed
awk
shell
14.搜索
Elasticsearch
定位
搜索引擎
nosql数据库
数据分析工具
数据结构
字符串
String
6.x以后不再支持
text
6.x以后用来代替string,用于吉检索
keyword
可用于排序
整数
浮点数
日期
布尔类型
binary
二进制不支持索引
数组
对象
特性
高性能
近实时
分布式,自集群
支持TCP和REST API
概念
index(索引)
数据库
type(类型)
表,不过一个索引 只有一个类型这里比不太合适
document(文档)
行
字段(field)
列
mapping(映射)
表结构
创建之后不能改
分片
路由
默认按_id 进行哈希路由到不同分片,保证所有分片数据均衡
hash(routing)%主分片数
主分片
主分片数不可变
副分片
相同分片的副分片不在同一个节点内
深度分页的劣势
版本
每次文档更新都会更新版本号
原理
倒排索引
ES集群
master节点
只有一个
脑裂
节点发现
ES自研的ZenDiscovery
负责集群管理
索引管理
不服务用户请求
data节点
存储数据
client节点
负载均衡
分布式
P2P类型的分布式系统(gossip协议)
相同cluster.name名称属于同一个集群
13.架构
架构设计
系统重构
中台能力
DDD领域建模
12.微服务
dubbo
架构
注册中心
Zookeeper
集群
脑裂问题
树存储
失效剔除 基于临时节点
simple
redis
multicast
提供者
消费者
核心配置
dubbo:service/dubbo:reference/dubbo:protocol/dubbo:registry/dubbo:application/dubbo:provider/dubbo:consumer/dubbo: method
通信框架(默认)
netty(默认)
mina
grizzly
远程调用(RPC)协议
dubbo协议(默认)
采用单一长链接和NIO异步通讯,适合于小数据量 大并发的服务调用,以及服务消费者机器远大于服务提供者机器的情况,不适合传输较大数据
Hession协议
底层采用Http通讯,使用多连接 短连接,同步传输,序列化成hessian二进制,适合传入 传出 参数数据包较大,提供者 比消费者个数多,提供者压力较大,可传文件。
Http协议
WebService协议
Redis协议
RMI协议
Thrift协议
Memcache协议
序列化
Hessian(默认)
dubbo
fastjson
java 自带序列化
负载均衡
RandomLoadBalance(默认)
随机,按权重设置随机概率
RoundRobinLoadBalance
轮循,按公约后的权重设置轮循比率
LeastActiveLoadBalance
最少活跃调用数,相同活跃数的随机。活跃数 指 调用前后计数差
ConsistentHashLoadBalance
一致性Hash,相同参数的请求 总是发到同一提供者
集群容错
SPI
实现原理
常见问题
spring cloud
注册中心
Eureka(Netflix 开源)
内部使用ConcurrentHashMap实现
Consul (Google 开源)
Nacos (阿里开源)
zookeeper(待验证)
服务网关
Zuul
网络路由
核心过滤器
pre
post
route
统一操作
降级
限流
认证
安全
异常处理
调用链监控
指标监控
日志监控
ELK
认证和授权
熔断和降级
Hystrix
隔离
线程隔离
在用户请求和服务之间加入了线程池
信号量的大小可以动态调整,线程池大小 不可以
信号隔离
熔断
熔断器在线程池 之间
状态
关闭状态
打开状态
半开(半熔断) 状态
降级
快速失败
不做处理,直接抛异常
无声失败
返回null ,空map ,空list
静态返回
返回默认值或自己组装一个值
远程缓存
在失败的情况下 再发起一次remote请求,不过这次请求的是一个缓存,比如redis 由于是又发起一起远程调用,所以会重新封装一次Command,这个时候要注意,执行fallback的线程 一定要和主线程分开,也就是重新命名一个ThreadPoolKey
主次方式退回
我们日常开发中需要上线一个新功能,为了防止 新功能上线失败,可以再新功能失败时 回退到老的代码
负载均衡
Ribbon
Feign
原理是动态代理
注解
配置中心
Apollo(携程开源)
Spring Cloud Config(基于Git)
容器化部署
docker
k8s
微服务领域,为什么选SpringCloud而不是Dubbo?
从开发难易度:dubbo需要依赖jar包,开发阶段难度极大;springcloud比较自由,但带来的问题是无法“强力约束接口规范”
从系统结构简易程序:springcloud的系统结构更简单、“注册+springmvc=springcloud”,而dubbo各种复杂的Url,protocol,register,invocation,dubbofilter,dubboSPI,dubbo序列化....炫技的成分更多一些
从整个大的平台架构来讲,dubbo框架只是专注于服务之间的治理,如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成,这样无形中使用dubbo的难度就会增加。Spring Cloud几乎考虑了服务治理的方方面面,更有Spring Boot这个大将的支持,开发起来非常的便利和简单。
从社区活跃度这个角度来对比,Dubbo虽然也是一个非常优秀的服务治理框架,并且在服务治理、灰度发布、流量分发这方面做的比Spring Cloud还好,除过当当网在基础上增加了rest支持外,已有两年多的时间几乎都没有任何更新了。在使用过程中出现问题,提交到github的Issue也少有回复
Spring Cloud来源于Spring,质量、稳定性、持续性都可以得到保证
SOA和微服务的区别
APM工具
Pinpoint
11.框架
Spring
IOC/DI
控制反转
对象A所依赖的对象B,由new 的方式转变为由容器进行注入的方式,
控制权由A反转到容器,DI是IOC的一种实现
原理
Xml解析
工厂模式
单例模式
反射
Bean
生命周期(总共11步)
1.初始化Bean,调用Bean构造器new 一个bean的实例
2.给Bean注入属性
3.如果Bean实现了BeanNameAware接口,调用setBeanName方法
4.如果Bean实现了BeanFactoryAware接口,调用setBeanFactory方法
5.如果有关联的BeanPostProcessor ,调用processBeforeInitialization (预初始化)方法
6.如果Bean实现了InitializatingBean,调用afterPropertiesSet方法
7.调用配置或注解Init-method方法
8.如果有关联的BeanPostProcessor,调用processPostInitialzation(后初始化)方法,跟6对比着看
9.Bean创建成功-执行业务逻辑,直到销毁
10.调用DispostbleBean 的destory方法
11.调用配置或注解的destory-method方法
加载过程
BeanFactory
获取时实例化Bean
ApplicationContext
继承BeanFactory
加载时实例化Bean
有参构造和无参构造,初始化
AOP
动态代理
https://baijiahao.baidu.com/s?id=1615034709376787673&wfr=spider&for=pc
jdk
原理:接口 实现接口:InvocationHandler
优点:
Jdk代理生成的代理类只有一个,因而其编译速度是非常快的;
缺点:
1.目标类被代理的方法必须实现一个接口
2.由于被代理的目标类是动态传入代理类中的,执行效率相对来说低一点,这也是Jdk代理被称为动态代理的原因
Jdk代理与目标类都会实现同一个接口,并且在代理类中会调用目标类中被代理的方法,调用者实际调用的则是代理类的方法
cglib(字节码)
原理:继承
优点:
代理类的方法是已经静态编译生成了的,因而Cglib代理的执行效率相对来说高一些
缺点:
Cglib代理需要为每个目标类生成相应的子类,因而在实际运行过程中,其可能会生成非常多的子类,影响了虚拟机编译类的效率
Javassist字节码
JBoss使用字节码实现AOP
字节码
ASM
事务(声明式事务)
事务有四个特性:ACID
Spring中七种事务传播行为
常用注解
@Controller
@Service
@Component
@Autowired、@Qualifier、@Resource
MyBatis
连接池
druid
密码加密
慢SQL
监控
动态SQL
crud
sql片段
foreach
接口、实体映射
分页
Spring Boot
简单
编码
配置
只需要很少的配置就能跑起来
部署
内置容器
监控
各种actuator
依赖管理
各种startor
parent,减少整个引用的依赖冲突
缺点
封装的太严重,很多问题暴露不出来
用好比较难。后面隐藏了整个Spring 的技术体系
Spring Security
OAuth2.0
JWT
JSON Web Token
三部分
头部Header
负载Payload
签名verify Signature
过滤器
授权
方法级
Web安全验证
01.Java
多线程
3.内存与JVM
内存模型(JMM)
堆(heap)
新生代
一块Eden
两块Survivor(From 和 To)
老年代
虚拟机栈(VM Stack)
本地方法栈(Native Method Stack)
方法区(Method Area)
运行时常量池(Runtime Constant Pool)
用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
程序计数器(Program Counter Register)
垃圾回收(GC)
垃圾收集算法
引用计数算法
缺点:无法处理循环引用的问题
根搜索算法(常用)
设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的
GC roots
虚拟机栈中的引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI的引用的对象
被同步锁(Synchronized关键字)持有的对象
除以上固定的GC Roots集合外,根据用户所选的垃圾收集器以及当前回收的区域不同,还可以有其他对象“临时性”的加入
垃圾回收算法
标记-清除算法(Mark-Sweep)
复制算法
标记-整理算法
分代收集算法
java sun hotspot虚拟机将内存分为新生代(堆)、老年代(堆)、永久代(方法区、常量池、即时编译代码)几个区域,
新生代主要使用基于复制算法的垃圾回收,老年代和永久代主要使用标记-整理算法进行垃圾回收。具体每个区域使用哪种垃圾回收算法还要视收集器的实现制约。
各区域回收规则(GC)
新生代(Minor-GC),默认占比8:1:1
Eden(伊甸区)
满了才GC
复制到其中一个Survivor中
Survivor(幸存者区)
满了才GC(GC 对象是 Eden+活动的Survivor)
将满足晋升阈值的对象复制到老年代
不满足晋升的复制到另一个Survivor
老年代
fullgc
收集器
https://www.cnblogs.com/zuoxiaolong/p/jvm7.html
三项重要指标(一款优秀的收集器通常最多可以同时达成其中的两项)
内存占用(Footprint)
吞吐量(Throughput)
延迟(Latency)
串行搜集器(serial collector)
概念:它只有一条GC线程,且就像前面说的,它在运行的时候需要暂停用户程序(stop the world)
serial(用于新生代,采用复制算法)
serial old(用于年老代,采用标记/整理算法)
并行搜集器(parallel collector)
概念:它有多条GC线程,且它也需要暂停用户程序(stop the world)
ParNew(用于新生代,采用复制算法,serial的并行爸版本)
Parallel Scavenge(用于新生代,采用复制算法)
Parallel old(用于年老代,采用标记/整理算法)
并发搜集器(concurrent collector)
概念:它有一条或多条GC线程,且它需要在部分阶段暂停用户程序(stop the world),部分阶段与用户程序并发执行。
【CMS】
concurrent mark sweep(用于年老代,采用标记/清除算法)
以获取最短回收停顿时间为目标的收集器
过程
初始化标记
需要停顿用户线程
并发标记
用户与GC线程并发运行
重新标记
需要停顿用户线程
并发清除
用户与GC线程并发运行
优点
并发收集
低停顿
内存回收进程与用户进程是一起工作的(重新标记、并发清楚)
缺点
对处理器资源非常敏感
默认启动的回收线程数=(处理器核心数量+3)/4
处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源
当核心数不足四个时,对用户程序的影响就变得很大。为缓解这情况,提供了“增量式并发收集器”【i-CMS】JDK7 已过时,JDK9 完全废弃
无法处理"浮动垃圾"
在CMS的并发标记和并发清理阶段,用户线程还是在继续运行的,期间会伴随新的垃圾对象不断产生,但是这部分垃圾是出现在标记过程之后,CMS无法在当前收集中处理它们,只好留待下一次垃圾回收时集中处理。
基于标记清除算法实现,会产生大量的空间碎片
空间碎片过多时,会出现老年代有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次FULL GC的情况,为解决这个问题,CMS提供了 【-XX:UseCMS - CompactAtFullCollection 】开关参数(默认开启,但 此参数JDK9开始废弃)。用于在不得不进行Full GC时 开启内存碎片的 合并整理过程,由于此过程必须移动存活对象,所以无法并发,导致停顿时间变长。
为解决这个问题,另外提供了参数【-XX:CMSFullGCsBefore Compation】(此参数从JDK9 开始废弃),其作用是 要求CMS在执行过若干次(数量又参数值觉得)不整理空间的Full GC之后,下一次进入Full GC之前,会先进行碎片整理(默认是值是0,每次进行Full GC前 都进行碎片整理)
【G1】
简介
采用了一种“细粒度”的内存你管理策略,不再是基于新生代(Minor GC)、老年代(Major GC)、整个Java堆(Full GC),而是面向堆内存任何部分来组成回收集(Collection Set,简称 CSet),进行回收。衡量标准不再是它属于哪个分代,而是哪块内存中存在的垃圾数量最多,回收 收益最大。这就是G1 收集器的Mixed GC模式
过程
初始标记
需要停顿用户线程
并发标记
用户与GC线程并发运行
最终标记
需要停顿用户线程
筛选回收
需要停顿用户线程
优点
可以由用户指定期望的停顿时间
默认200ms
分Region的内存布局、按收益动态确定回收集
不会产生内存碎片
与CMS的“标记-清除”算法不同,G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现
缺点
比起CMS,G1的弱项也可以列举出不少,如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS要高。
虽然G1和CMS都使用卡表来处理跨代指针,但G1的卡表实现更为复杂,而且堆中每个Region,无论扮演的是新生代还是老年代角色,都必须有一份卡表,这导致G1的记忆集(和其他内存消耗)可能会占整个堆容量的20%乃至更多的内存空间
【ZGC】
低延迟垃圾收集器
Shenandoah收集器
简介
更像是G1的下一代继承者,它们两者有着相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路上都高度一致,甚至还直接共享了一部分实现代码,这使得部分对G1的打磨改进和Bug修改会同时反映在Shenandoah之上,而由于Shenandoah加入所带来的一些新特性,也有部分会出现在G1收集器中,譬如在并发失败后作为“逃生门”的Full GC[插图],G1就是由于合并了Shenandoah的代码才获得多线程Full GC的支持
相比起G1的改进
虽然Shenandoah也是使用基于Region的堆内存布局,同样有着用于存放大对象的Humongous Region,默认的回收策略也同样是优先处理回收价值最大的Region……但在管理堆内存方面,它与G1至少有三个明显的不同之处。
最重要的当然是支持并发的整理算法,G1的回收阶段是可以多线程并行的,但却不能与用户线程并发,这点作为Shenandoah最核心的功能稍后笔者会着重讲解。 【TODO】
其次,Shenandoah(目前)是默认不使用分代收集的,换言之,不会有专门的新生代Region或者老年代Region的存在,没有实现分代,并不是说分代对Shenandoah没有价值,这更多是出于性价比的权衡,基于工作量上的考虑而将其放到优先级较低的位置上
最后摒弃了在G1中耗费大量内存和计算资源去维护的记忆集,改用名为“连接矩阵”(Connection Matrix)的全局数据结构来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题的发生概率。连接矩阵可以简单理解为一张二维表格
过程(重点:并发标记、回收、引用更新)
初始标记
与G1一样,首先标记与GC Roots直接关联的对象,这个阶段仍是“Stop The World”的,但停顿时间与堆大小无关,只与GC Roots的数量相关。
并发标记
与G1一样,遍历对象图,标记出全部可达的对象,这个阶段是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。
最终标记
与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记阶段也会有一小段短暂的停顿。
并发清理
这个阶段用于清理那些整个区域内连一个存活对象都没有找到的Region(这类Region被称为Immediate Garbage Region)
并发回收
并发回收阶段是Shenandoah与之前HotSpot中其他收集器的核心差异。在这个阶段,Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。复制对象这件事情如果将用户线程冻结起来再做那是相当简单的,但如果两者必须要同时并发进行的话,就变得复杂起来了。其困难点是在移动对象的同时,用户线程仍然可能不停对被移动的对象进行读写访问,移动对象是一次性的行为,但移动之后整个内存中所有指向该对象的引用都还是旧对象的地址,这是很难一瞬间全部改变过来的。对于并发回收阶段遇到的这些困难,Shenandoah将会通过读屏障和被称为“Brooks Pointers”的转发指针来解决。并发回收阶段运行的时间长短取决于回收集的大小。
初始化引用更新
并发回收阶段复制对象结束后,还需要把堆中所有指向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。
并发引用更新
真正开始进行引用更新操作,这个阶段是与用户线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。并发引用更新与并发标记不同,它不再需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为新值即可。
最终引用更新
解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。这个阶段是Shenandoah的最后一次停顿,停顿时间只与GC Roots的数量相关。
并发清理
ZGC
ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。
ZGC和Shenandoah的目标是高度相似的,都希望在尽可能对吞吐量影响不太大的前提下[插图],实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。但是ZGC和Shenandoah的实现思路又是差异显著的,如果说RedHat公司开发的Shen-andoah像是Oracle的G1收集器的实际继承者的话,那Oracle公司开发的ZGC就更像是Azul System公司独步天下的PGC(Pauseless GC)和C4(Concurrent Continuously Compacting Collector)收集器的同胞兄弟。
内存布局
与Shenandoah和G1一样,ZGC也采用基于Region的堆内存布局,但与它们不同的是,ZGC的Region(在一些官方资料中将它称为Page或者ZPage,本章为行文一致继续称为Region)具有动态性——动态创建和销毁,以及动态的区域容量大小。在x64硬件平台下,ZGC的Region可以具有大、中、小三类容量:
小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象
中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。
染色指针的三大优势
染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理。
染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,设置内存屏障,尤其是写屏障的目的通常是为了记录对象引用的变动情况,如果将这些信息直接维护在指针中,显然就可以省去一些专门的记录操作。
过程
初始化
与G1和Shenandoah的Initial Mark阶段相同
并发标记(Concurrent Mark)
与G1、Shenandoah一样,并发标记是遍历对象图做可达性分析的阶段,前后也要经过类似于G1、Shenandoah的初始标记、最终标记(尽管ZGC中的名字不叫这些)的短暂停顿,而且这些停顿阶段所做的事情在目标上也是相类似的。与G1、Shenandoah不同的是,ZGC的标记是在指针上而不是在对象上进行的,标记阶段会更新染色指针中的Marked 0、Marked 1标志位。
并发预备重分配(Concurrent Prepare for Relocate)
这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。重分配集与G1收集器的回收集(Collection Set)还是有区别的,ZGC划分Region的目的并非为了像G1那样做收益优先的增量回收。相反,ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。因此,ZGC的重分配集只是决定了里面的存活对象会被重新复制到其他的Region中,里面的Region会被释放,而并不能说回收行为就只是针对这个集合里面的Region进行,因为标记过程是针对全堆的。此外,在JDK 12的ZGC中开始支持的类卸载以及弱引用的处理,也是在这个阶段中完成的。
并发重分配(Concurrent Relocate)
重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。得益于染色指针的支持,ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。
这个阶段的优点
这样做的好处是只有第一次访问旧对象会陷入转发,也就是只慢一次,对比Shenandoah的Brooks转发指针,那是每次对象访问都必须付出的固定开销,简单地说就是每次都慢,因此ZGC对用户程序的运行时负载要比Shenandoah来得更低一些。还有另外一个直接的好处是由于染色指针的存在,一旦重分配集中某个Region的存活对象都复制完毕后,这个Region就可以立即释放用于新对象的分配(但是转发表还得留着不能释放掉),哪怕堆中还有很多指向这个对象的未更新指针也没有关系,这些旧指针一旦被使用,它们都是可以自愈的。
并发重映射(Concurrent Remap)
重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,这一点从目标角度看是与Shenandoah并发引用更新阶段一样的,但是ZGC的并发重映射并不是一个必须要“迫切”去完成的任务,因为前面说过,即使是旧引用,它也是可以自愈的,最多只是第一次使用时多一次转发和修正操作。重映射清理这些旧引用的主要目的是为了不变慢(还有清理结束后可以释放转发表这样的附带收益),所以说这并不是很“迫切”。因此,ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图[插图]的开销。一旦所有指针都被修正之后,原来记录新旧对象关系的转发表就可以释放掉了。
优点
与Shenandoah一样做到了几乎整个收集过程都全程可并发,短暂停顿也只与GC Roots大小相关而与堆内存大小无关,因而同样实现了任何堆上停顿都小于十毫秒的目标
ZGC就完全没有使用记忆集,它甚至连分代都没有,连像CMS中那样只记录新生代和老年代间引用的卡表也不需要,因而完全没有用到写屏障,所以给用户线程带来的运行负担也要小得多
支持“NUMA-Aware”的内存分配
缺点
并发收集的时间段内,可能会产生大量的浮动垃圾,回收到的内存空间持续小于期间并发产生的浮动垃圾所占的空间,堆中剩余可腾挪的空间就越来越小了。
解决办法
目前唯一的办法就是尽可能地增加堆容量大小,获得更多喘息的时间。但是若要从根本上提升ZGC能够应对的对象分配速率,还是需要引入分代收集,让新生对象都在一个专门的区域中创建,然后专门针对这个区域进行更频繁、更快的收集
C4
选择合适的垃圾收集器
GC
Minor GC
从年轻代空间(包括Eden 和Survivor 区域)回收内存被称为Minor GC
Major GC
清理老年代
Full GC
清理整个堆空间-包括年轻代和老年代
类的生命周期
(加载过程/机制)
加载
把class文件中的字节码通过类加载器装载入内存中
类加载器
启动类加载器(BootStrap Classloader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(App ClassLoader)
自定义类加载器(Custom ClassLoader)
双亲委派模型
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载
如何破坏?线程上下文类加载器:如 热部署
原理?
缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
链接
验证
保证加载进来的字节流符合虚拟机规范,不会造成安全错误
准备
为类变量(注意,不是实例变量)分配内存,并且赋予初值
(注:不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。)
解析
将常量池内的符号引用替换为直接引用的过程。
符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息
直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针
初始化
主要是对类变量初始化,是执行类构造器的过程
使用
卸载
调优
大对象fllgc导致的stop the world 时间过长
降低进入老年代的对象数
增大年轻代空间
增加对象晋升的年龄阈值
缩短fullgc时间
减少 老年代空间
可能会导致更频繁的oom,所以需要调整成一个“合适”的值
oom
永久代
调大permsize
JVM参数
-Xmx256m(设置堆最大值)
-Xms128(设置初始化堆大小)
-xss128k(设置线程栈大小)
-Xmn2g(设置年轻代大小)
-XX:NewSize=1024m(设置年轻代 初始值)
-XX:MaxNewSize=1024(设置年轻代最大值)
-XX:PermSize=256m 设置永久代初始值
-XX:MaxpermSize=256m 设置永久代最大值
-XX:NewRatio=4(设置年轻代和年老代的比值)
-XX:SurvivorRatio=4设置Suvivor区和Eden区的比值
-XX:MaxTenuringThreshold=7 (表示一个对象从年轻代移入年老代的年龄)
内存分配机制
内存泄露与监控
程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。
大量的内存泄露会导致内存溢出
引用状态
强引用
代码中普遍存在的类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
软引用
描述 有些还有用但并非必须的对象。再系统将要发生溢出异常之前,将会把这些对象列进回收范围 进行二次回收。如果这次回收 还没有足够的内存,才会抛出内存溢出异常。Java 中的类SoftReference 表示软引用。
弱引用
描述 非必需对象。被弱引用关联的对象 只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前是否足够,都会回收掉 只被弱引用 关联的对象。
Java 中的类 WeakReferance 表示弱引用
虚引用
这个引用存在的唯一目的就是 在这个对象被收集器回收时 收到一个系统通知,被虚引用 关联的对象,和生存时间完全没关系。
Java中的类 PhantomReference表述虚引用
内存模型
重排序
volatile重排序规则
1.当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序
2.当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
3.当第一个操作是volatile写,第二个是volatile读时,不能重排序
final重排序规则
JMM禁止将final域内的写重排序到构造函数之外
顺序一致性
Happens-before
JVM常见面试题
https://www.cnblogs.com/nov5026/p/11013492.html
2.JAVA基础
数据类型
基本类型
整形
byte short int long cahr
浮点型
double float
boolean
引用类型
类 接口 数组
字符串
面向对象
类 和 对象
枚举 enum
enum 与接口常量
集合类
集成与多态
泛型
内部类
接口
注解
异常与错误
正则表达式
Pattern 和 Matcher
Java7与Java8差异
集合
Collection
List
ArrayList
基于数组
多读场景
LinkList
基于链表
多读场景
Vector
线程安全
Set
HashSet
TreeSet
LinkedHashSet
Queue
不阻塞队列
ConcurrentLinkedQueue
阻塞队列
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue(双端队列)
知识点:原理、结构、性能、排序、线程安全、性能、大小、扩容机制
Map
AbstractMap
HashMap
允许null
线程不全
ConcurrentHashMap
线程安全
get方式不加锁,使用getObjectVolatile保证获取到最新值
分段锁(1.8 使用红黑树,时间复杂度O(log(n)))
Hashtable
不允许null
线程安全
LinkedHashMap
有序(插入顺序)
TreeMap
有序(自然排序、实现SortedMap)
使用红黑树
高效操作几个的方式
并行流
异步流CompletableFuture
常用类
String
new String("ABC") 当常量池中存在对象ABC时,创建一个堆对象;否则创建两个对象,堆对象+常量池对象
Date
基本语法
运算符
自增 自减
访问控制
public private protecated
循环条件
if while for break continue
异常
反射
02.算法
搜索算法
排序算法
稳定
冒泡排序
O(n2)
插入排序
新插入元素与 前面的队列比较
O(n*log2n)
快速排序
中轴、分治排序
O(n*log2n)
归并排序
分解、求解、合并
O(n*log2n)
TimSort
结合了合并排序和插入排序;块排序
O(n log n)
比较类排序算法
交换排序
冒泡排序
快速排序
插入排序
简单插入排序
希尔排序
选择排序
简单选择排序
堆排序
归并排序
二路归并排序
多路归并排序
TimSort
非比较类排序算法
计数排序
通排序
基数排序
03.数据结构
线性结构
链表 hash
树形结构
树 二叉树
图
图的搜索遍历
广度优先
深度优先
04.设计模式
原则
单例模式
懒汉模式
线程不安全
可以使用同步变成线程安全
懒加载
饿汉模式
线程安全
提前初始化,非懒加载
静态内部类
线程安全
双重校验锁
枚举
工厂模式
简单工厂模式
switch 或者if else 实现
工厂模式
抽象工厂模式
模板模式
策略模式
适配器模式
源-被转换的角色
适配器
目标-将要转换成的角色
更好的复用性
更好的拓展性
内部实现复制凌乱,要进去才知道具体的调用实现
观察者模式
观察着模式
被观察者维护观察者列表
发布-订阅
发布者和订阅者 通过委托处理
发布者和订阅者完全解耦
命令模式
建造者模式
外观模式
桥接模式
代理模式
动态代理
JDK
基于接口
InvocationHader
CGLB
基于继承
MethodInterceptor
静态代理
装饰着模式
顶级抽象接口
被装饰者
装饰者
装饰子类
行为模式
结构模式
创建模式
05.数据库
Oracle
TableStore
MongoDB
MySql
基于B+树
B树:有序数组+平衡多叉树;B+树:有序数组链表+平衡多叉树;
架构
连接层
最上层是一些客户端和连接服务。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层
第二层服务层,主要完成大部分的核心服务功能, 包括查询解析、分析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等
引擎层
第三层存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取
存储层
第四层为数据存储层,主要是将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互
面试题
画出 MySQL 架构图
MySQL 的查询流程具体是?or 一条SQL语句在MySQL中如何执行的?
客户端请求 ---> 连接器(验证用户身份,给予权限) ---> 查询缓存(存在缓存则直接返回,不存在则执行后续操作) ---> 分析器(对SQL进行词法分析和语法分析操作) ---> 优化器(主要对执行的sql优化选择最优的执行方案方法) ---> 执行器(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) ---> 去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果)
分支主题
存储引擎
InnoDB
支持事务
支持行级锁
不支持全文检索
MyISAM
不支持事务
表锁
全文检索
面试题
说说MySQL有哪些存储引擎?都有哪些区别?
InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
InnoDB 不保存表的具体行数,执行select count(*) from table 时需要全表扫描。而 MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
一张表,里面有ID自增主键,当insert了17条记录之后,删除了第15,16,17条记录,再把Mysql重启,再insert一条记录,这条记录的ID是18还是15 ?
如果表的类型是MyISAM,那么是18。因为MyISAM表会把自增主键的最大ID 记录到数据文件中,重启MySQL自增主键的最大ID也不会丢失;
如果表的类型是InnoDB,那么是15。因为InnoDB 表只是把自增主键的最大ID记录到内存中,所以重启数据库或对表进行OPTION操作,都会导致最大ID丢失。
哪个存储引擎执行 select count(*) 更快,为什么?
MyISAM更快,因为MyISAM内部维护了一个计数器,可以直接调取。
在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,当执行 select count(*) from t 时,直接返回总数据。
在 InnoDB 存储引擎中,跟 MyISAM 不一样,没有将总行数存储在磁盘上,当执行 select count(*) from t 时,会先把数据读出来,一行一行的累加,最后返回总数量。
InnoDB 中 count(*) 语句是在执行的时候,全表扫描统计总数量,所以当数据越来越大时,语句就越来越耗时了,为什么 InnoDB 引擎不像 MyISAM 引擎一样,将总行数存储到磁盘上?这跟 InnoDB 的事务特性有关,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。
数据类型
整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT
浮点数类型:FLOAT、DOUBLE、DECIMAL
字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB
日期类型:Date、DateTime、TimeStamp、Time、Year
其他数据类型:BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等
面试题
CHAR 和 VARCHAR 的区别?
相同点
char(n),varchar(n)中的n都代表字符的个数
超过char,varchar最大长度n的限制后,字符串会被截断。
不同点
char是固定长度,varchar长度可变,char不论实际存储的字符数都会占用n个字符的空间,而varchar只会占用实际字符应该占用的字节空间加1(实际长度length,0<=length<255)或加2(length>255)。因为varchar保存数据时除了要保存字符串之外还会加一个字节来记录长度(如果列声明长度大于255则使用两个字节来保存长度)。
能存储的最大空间限制不一样:char的存储上限为255字节。
char在存储时会截断尾部的空格,而varchar不会。
列的字符串类型可以是什么?
字符串类型是:SET、BLOB、ENUM、CHAR、CHAR、TEXT、VARCHAR
BLOB和TEXT有什么区别?
BLOB是一个二进制对象,可以容纳可变数量的数据。有四种类型的BLOB:TINYBLOB、BLOB、MEDIUMBLO和 LONGBLOB
TEXT是一个不区分大小写的BLOB。四种TEXT类型:TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。
BLOB 保存二进制数据,TEXT 保存字符数据
索引
B+Tree索引
索引的优劣势
优势
提高数据检索效率,降低数据库IO成本
降低数据排序的成本,降低CPU的消耗
劣势
索引也是一张表,保存了主键和索引字段,并指向实体表的记录,所以也需要占用内存
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段, 都会调整因为更新所带来的键值变化后的索引信息
索引分类
数据结构角度
B+树索引
Hash索引
Full-Text全文索引
R-Tree索引
从物理存储角度
聚集索引(clustered index)
(每个表只能有一个聚簇索引)聚簇索引的数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。
非聚集索引(non-clustered index),也叫辅助索引(secondary index)
每个表你最多可以建立249个非聚簇索引。非聚簇索引需要大量的硬盘空间和内存。另外,虽然非聚簇索引可以提高从表中取数据的速度,它也会降低向表中插入和更新数据的速度。每当你改变了一个建立了非聚簇索引的表中的数据时,必须同时更新索引。因此你对一个表建立非聚簇索引时要慎重考虑。如果你预计一个表需要频繁地更新数据,那么不要对它建立太多非聚簇索引。
聚集索引和非聚集索引都是B+树结构
从逻辑角度
主键索引:主键索引是一种特殊的唯一索引,不允许有空值
普通索引或者单列索引:每个索引只包含单个列,一个表可以有多个单列索引
多列索引(复合索引、联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合
唯一索引或者非唯一索引
空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建
B-Tree 和 B+Tree 的区别
B-Tree
简介
B-Tree是为磁盘等外存储设备设计的一种平衡查找树。
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB,可通过参数 innodb_page_size 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:show variables like 'innodb_page_size';
而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
特性(m阶树)
每个节点最多有m个孩子
除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
若根节点不是叶子节点,则至少有2个孩子
所有叶子节点都在同一层,且不包含其它关键字信息
每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
ki(i=1,…n)为关键字,且关键字升序排序
Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
查找过程(如 右图查关键字29)
根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
比较关键字29在区间(17,35),找到磁盘块1的指针P2。
根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
比较关键字29在区间(26,30),找到磁盘块3的指针P2。
根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
在磁盘块8中的关键字列表中找到关键字29。
B+Tree
简介
B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是用 B+Tree 实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度
B+Tree相对于B-Tree有几点不同
非叶子节点只存储键值信息;
所有叶子节点之间都有一个链指针;
数据记录都存放在叶子节点中
B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
分支主题
B+Tree性质
通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
MyISAM主键索引与辅助索引的结构
MyISAM引擎的索引文件和数据文件是分离的。MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。索引文件与数据文件分离,这样的索引称为"非聚簇索引"。
在MyISAM中,索引(含叶子节点)存放在单独的.myi文件中,叶子节点存放的是数据的物理地址偏移量
主索引是指主键索引,键值不可能重复;辅助索引则是普通索引,键值可能重复。
通过索引查找数据的流程:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。辅助索引类似。
InnoDB主键索引与辅助索引的结构
InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,InnoDB的数据文件本身就是主键索引文件,这样的索引被称为“聚簇索引”
主键索引
InnoDB索引是聚集索引,它的索引和数据是存入同一个.idb文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据
索引分叶子节点和非叶子节点,非叶子节点就像新华字典的目录,单独存放在索引段中,叶子节点则是顺序排列的,在数据段中。Innodb的数据文件可以按照表来切分(只需要开启innodb_file_per_table),切分后存放在xxx.ibd中,默认不切分,存放在xxx.ibdata中
辅助(非主键)索引
它的索引结构跟主键索引的结构有很大差别,在最底层的叶子结点有两行数据,第一行的字符串是辅助索引,按照ASCII码进行排序,第二行的整数是主键的值。
查找过程
在辅助索引上检索name,到达其叶子节点获取对应的主键;
使用主键在主索引上再进行对应的检索操作
注意事项
数据文件本身就是索引文件
表数据文件本身就是按 B+Tree 组织的一个索引结构文件
聚集索引中叶节点包含了完整的数据记录
InnoDB 表必须要有主键,并且推荐使用整型自增主键
Hash索引
主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。
检索算法:在检索查询时,就再次对待查关键字再次执行相同的Hash算法,得到Hash值,到对应Hash表对应位置取出数据即可,如果发生Hash碰撞,则需要在取值时进行筛选。目前使用Hash索引的数据库并不多,主要有Memory等。
MySQL目前有Memory引擎和NDB引擎支持Hash索引。
full-text全文索引
全文索引也是MyISAM的一种特殊索引类型,主要用于全文索引,InnoDB从MYSQL5.6版本提供对全文索引的支持。
它用于替代效率较低的LIKE模糊匹配操作,而且可以通过多字段组合的全文索引一次性全模糊匹配多个字段。
同样使用B-Tree存放索引数据,但使用的是特定的算法,将字段数据分割后再进行索引(一般每4个字节一次分割),索引文件存储的是分割前的索引字符串集合,与分割后的索引信息,对应Btree结构的节点存储的是分割后的词信息以及它在分割前的索引字符串集合中的位置。
R-Tree空间索引
空间索引是MyISAM的一种特殊索引类型,主要用于地理空间数据类型
MySQL高效索引
覆盖索引
覆盖索引(Covering Index),或者叫索引覆盖, 也就是平时所说的不需要回表操作
就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据,当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。
判断标准
使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为using index,MySQL查询优化器在执行查询前会决定是否有索引覆盖查询
面试题
说说你对 MySQL 索引的理解?
官方对索引的定义:索引(Index)是帮助MySQL高效获取数据的数据结构,所以说索引的本质是:数据结构
索引的目的在于提高查询效率,可以类比字典、 火车站的车次表、图书的目录等
可以简单的理解为“排好序的快速查找数据结构”,数据本身之外,数据库还维护者一个满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引
索引本身也很大,不可能全部存储在内存中,一般以索引文件的形式存储在磁盘上
平常说的索引,没有特别指明的话,就是B+树(多路搜索树,不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引,符合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。此外还有哈希索引等。
那为什么推荐使用整型自增主键而不是选择UUID?
UUID是字符串,比整型消耗更多的存储空间;
在B+树中进行查找时需要跟经过的节点值比较大小,整型数据的比较运算比字符串更快速;
自增的整型索引在磁盘中会连续存储,在读取一页数据时也是连续;UUID是随机产生的,读取的上下两行数据存储是分散的,不适合执行where id > 5 && id < 20的条件查询语句。
在插入或删除数据时,整型自增主键会在叶子结点的末尾建立新的叶子节点,不会破坏左侧子树的结构;UUID主键很容易出现这样的情况,B+树为了维持自身的特性,有可能会进行结构的重构,消耗更多的时间。
为什么非主键索引结构叶子节点存储的是主键值?
保证数据一致性和节省存储空间,可以这么理解:商城系统订单表会存储一个用户ID作为关联外键,而不推荐存储完整的用户信息,因为当我们用户表中的信息(真实名称、手机号、收货地址···)修改后,不需要再次维护订单表的用户数据,同时也节省了存储空间。
为什么Mysql索引要用B+树不是B树?
用B+树不用B树考虑的是IO对性能的影响,B树的每个节点都存储数据,而B+树只有叶子节点才存储数据,所以查找相同数据量的情况下,B树的高度更高,IO更频繁。数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘页(对应索引树的节点)。其中在MySQL底层对B+树进行进一步优化:在叶子节点中是双向链表,且在链表的头结点和尾节点也是循环指向的。
为何不采用Hash方式?
因为Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ Tree是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描。
哈希索引不支持多列联合索引的最左匹配规则,如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题。
哪些情况需要创建索引?
主键自动建立唯一索引
频繁作为查询条件的字段
查询中与其他表关联的字段,外键关系建立索引
单键/组合索引的选择问题,高并发下倾向创建组合索引
查询中排序的字段,排序字段通过索引访问大幅提高排序速度
查询中统计或分组字段
哪些情况不要创建索引?
表记录太少
经常增删改的表
数据重复且分布均匀的表字段,只应该为最经常查询和最经常排序的数据列建立索引(如果某个数据类包含太多的重复数据,建立索引没有太大意义)
频繁更新的字段不适合创建索引(会加重IO负担)
where条件里用不到的字段不创建索引
MySQL查询
SQL执行顺序
分支主题
面试题
count(*) 和 count(1)和count(列名)区别?
执行效果上
count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
count(1)包括了所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。
执行效率上
列名为主键,count(列名)会比count(1)快
列名不为主键,count(1)会比count(列名)快
如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(*)
如果有主键,则 select count(主键)的执行效率是最优的
如果表只有一个字段,则 select count(*) 最优。
MySQL中 in和 exists 的区别?
exists:exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当exists里的条件语句能够返回记录行时(无论记录行是的多少,只要能返回),条件就为真,返回当前loop到的这条记录;反之,如果exists里的条件语句不能返回记录行,则当前loop到的这条记录被丢弃,exists的条件就像一个bool条件,当能返回结果集则为true,不能返回结果集则为false
in:in查询相当于多个or条件的叠加
如果查询的两个表大小相当,那么用in和exists差别不大
如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in
UNION和UNION ALL的区别?
UNION和UNION ALL都是将两个结果集合并为一个,两个要联合的SQL语句 字段个数必须一样,而且字段类型要“相容”(一致);
UNION在进行表连接后会筛选掉重复的数据记录(效率较低),而UNION ALL则不会去掉重复的数据记录;
UNION会按照字段的顺序进行排序,而UNION ALL只是简单的将两个结果合并就返回;
mysql 的内连接、左连接、右连接有什么区别?
什么是内连接、外连接、交叉连接、笛卡尔积呢?
事务
四大特性ACID
原子性(atomicity)
一致性(consistency)
隔离性(isolation)
通过锁实现
持久性(durability)
并发事务处理带来的问题
更新丢失
事务A和事务B选择同一行,然后基于最初选定的值更新该行时,由于两个事务都不知道彼此的存在,就会发生丢失更新问题
脏读
事务A读到了事务B还没有提交的旧数据
不可重复读
在一个事务里面读取了两次某个数据,读出来的数据不一致
幻读
在一个事务里面的操作中发现了未被操作的数据
幻读和不可重复读的区别
不可重复读的重点是修改
在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改)
幻读的重点在于新增或者删除
在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除)
事务隔离级别
读未提交(READ_UNCOMMITTED)->脏读、幻读
即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
读已提交(READ_COMMITED)->不可重复度、幻读
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
重复读取(REPEATABLE_READ)->幻读
即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
串行化(SERLALIZABLE)
最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
并发事务处理带来的问题的解决办法
“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读” 、 “不可重复读”和“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决:
一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。
另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 MVCC 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。
MVCC 多版本并发控制
简介
MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只是锁定必要的行。
MVCC 的实现是通过保存数据在某个时间点的快照来实现的。也就是说不管需要执行多长时间,每个事物看到的数据都是一致的。
MVCC实现方式
分为乐观(optimistic)并发控制
悲观(pressimistic)并发控制
InnoDB 的 MVCC
通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存行的过期时间(删除时间)。当然存储的并不是真实的时间,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较
REPEATABLE READ(可重读)隔离级别下MVCC如何工作?
SELECT
InnoDB会根据以下两个条件检查每行记录:只有符合上述两个条件的才会被查询出来
InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在开始事务之前已经存在要么是事务自身插入或者修改过的
行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除
INSERT
InnoDB为新插入的每一行保存当前系统版本号作为行版本号
DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识
UPDATE
InnoDB为插入的一行新纪录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识
MVCC 只在 COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作
事务日志
简介
InnoDB 使用日志来减少提交事务时的开销。因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新(flush)到磁盘中
事务修改的数据和索引通常会映射到表空间的随机位置,所以刷新这些变更到磁盘需要很多随机 IO。
InnoDB 假设使用常规磁盘,随机IO比顺序IO昂贵得多,因为一个IO请求需要时间把磁头移到正确的位置,然后等待磁盘上读出需要的部分,再转到开始位置。
InnoDB 用日志把随机IO变成顺序IO。一旦日志安全写到磁盘,事务就持久化了,即使断电了,InnoDB可以重放日志并且恢复已经提交的事务。
InnoDB 使用一个后台线程智能地刷新这些变更到数据文件。这个线程可以批量组合写入,使得数据写入更顺序,以提高效率。
事务日志可以帮助提高事务效率
使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。
事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。
事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。
如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。
目前来说,大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。
事务日志包括
redo log(重做日志)
实现持久化和原子性
在innoDB的存储引擎中,事务日志通过重做(redo)日志和innoDB存储引擎的日志缓冲(InnoDB Log Buffer)实现。事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是DBA们口中常说的“日志先行”(Write-Ahead Logging)。当事务提交之后,在Buffer Pool中映射的数据文件才会慢慢刷新到磁盘。此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据redo log中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。
在系统启动的时候,就已经为redo log分配了一块连续的存储空间,以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。所有的事务共享redo log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起。
undo log(回滚日志)
实现一致性
undo log 主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。
Undo记录的是已部分完成并且写入硬盘的未完成的事务,默认情况下回滚日志是记录下表空间中的(共享表空间或者独享表空间)
事务的实现
事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持程度不一样。MySQL 中支持事务的存储引擎有 InnoDB 和 NDB。
事务的实现就是如何实现ACID特性。
事务的隔离性是通过锁实现,而事务的原子性、一致性和持久性则是通过事务日志实现 。
事务是如何通过日志来实现的? 说得越深入越好。
MySQL对分布式事务的支持
分布式事务的实现方式有很多,既可以采用 InnoDB 提供的原生的事务支持,也可以采用消息队列来实现分布式事务的最终一致性。这里我们主要聊一下 InnoDB 对分布式事务的支持。MySQL 从 5.0.3 InnoDB 存储引擎开始支持XA协议的分布式事务。一个分布式事务会涉及多个行动,这些行动本身是事务性的。所有行动都必须一起成功完成,或者一起被回滚。
在MySQL中,使用分布式事务涉及一个或多个资源管理器和一个事务管理器。
MySQL 的分布式事务模型
应用程序(AP)
定义了事务的边界,指定需要做哪些事务
资源管理器(RM)
提供了访问事务的方法,通常一个数据库就是一个资源管理器
事务管理器(TM)
协调参与了全局事务中的各个事务
分布式事务采用两段式提交(two-phase commit)的方式
第一阶段所有的事务节点开始准备,告诉事务管理器ready
第二阶段事务管理器告诉每个节点是commit还是rollback。如果有一个节点失败,就需要全局的节点全部rollback,以此保障事务的原子性
面试题
事务的隔离级别有哪些?MySQL的默认隔离级别是什么?
默认级别是 Repeatable read
什么是幻读,脏读,不可重复读呢?
MySQL事务的四大特性以及实现原理
MVCC熟悉吗,它的底层原理?
你知道MySQL 有多少种日志吗?
分布式事务相关问题,可能还会问到 2PC、3PC
锁机制
锁的分类
读锁(共享锁)
针对同一份数据,多个读操作可以同时进行,不会互相影响
写锁(排他锁)
当前写操作没有完成前,它会阻断其他写锁和读锁
从对数据操作的粒度分类
表级锁
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低(MyISAM 和 MEMORY 存储引擎采用的是表级锁);
行级锁
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高(InnoDB 存储引擎既支持行级锁也支持表级锁,但默认情况下是采用行级锁);
页面锁
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用
从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统
MyISAM 表锁(有两种模式)
表共享读锁 (Table Read Lock)
不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
表独占写锁 (Table Write Lock)
会阻塞其他用户对同一表的读和写操作
InnoDB 行锁(有两种类型)
共享锁(S)
允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁
排他锁(X)
允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁
意向锁(属于表锁)
允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁
意向共享锁(IS)
事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁
意向排他锁(IX)
事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁
索引失效会导致行锁变表锁。比如 vchar 查询不写单引号的情况。
加锁机制
乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题
乐观锁
“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务。用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式
悲观锁
“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
锁模式(InnoDB有三种行锁的算法)
记录锁(Record Locks)
单个行记录上的锁。对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项
SELECT * FROM table WHERE id = 1 FOR UPDATE;
它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行
在通过 主键索引 与 唯一索引 对数据行进行 UPDATE 操作时,也会对该行数据加记录锁:
UPDATE SET age = 50 WHERE id = 1;
间隙锁(Gap Locks)
当我们使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但并不存在的记录,叫做“间隙”
间隙锁基于非唯一索引,它锁定一段范围内的索引记录。间隙锁基于下面将会提到的Next-Key Locking 算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
即所有在(1,10)区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。
GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况
临键锁(Next-key Locks)
是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。)
死锁
死锁产生
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环
当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁
锁的行为和顺序和存储引擎相关。以同样的顺序执行语句,有些存储引擎会产生死锁有些不会——死锁有双重原因:真正的数据冲突;存储引擎的实现方式。
检测死锁
数据库系统实现了各种死锁检测和死锁超时的机制。InnoDB存储引擎能检测到死锁的循环依赖并立即返回一个错误。
死锁恢复
死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁,InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。所以事务型应用程序在设计时必须考虑如何处理死锁,多数情况下只需要重新执行因死锁回滚的事务即可。
外部锁的死锁检测
发生死锁后,InnoDB 一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB 并不能完全自动检测到死锁, 这需要通过设置锁等待超时参数 innodb_lock_wait_timeout 来解决
死锁影响性能
死锁会影响性能而不是会产生严重错误,因为InnoDB会自动检测死锁状况并回滚其中一个受影响的事务。在高并发系统上,当许多线程等待同一个锁时,死锁检测可能导致速度变慢。有时当发生死锁时,禁用死锁检测(使用innodb_deadlock_detect配置选项)可能会更有效,这时可以依赖innodb_lock_wait_timeout设置进行事务回滚。
MyISAM避免死锁
在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,所以 MyISAM 表不会出现死锁。
InnoDB避免死锁
为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个元祖(行)使用SELECT ... FOR UPDATE语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。
在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁
如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会
通过SELECT ... LOCK IN SHARE MODE获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。
改变事务隔离级别
如果出现死锁,可以用 show engine innodb status;命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施
级别
类型
mvcc
面试题
数据库的乐观锁和悲观锁?
MySQL 中有哪几种锁,列举一下?
MySQL中InnoDB引擎的行锁是怎么实现的?
MySQL 间隙锁有没有了解,死锁有没有了解,写一段会造成死锁的 sql 语句,死锁发生了如何解决,MySQL 有没有提供什么机制去解决死锁
select for update有什么含义,会锁表还是锁行还是其他
for update 仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!假设有个表单 products ,里面有id跟name二个栏位,id是主键。
MySQL 遇到过死锁问题吗,你是如何解决的?
性能优化
影响mysql的性能因素
业务需求对MySQL的影响(合适合度)
存储定位对MySQL的影响
系统各种配置及规则数据
活跃用户的基本信息数据
活跃用户的个性化定制信息数据
准实时的统计信息数据
其他一些访问频繁但变更较少的数据
二进制多媒体数据
流水队列数据
超大文本数据
不适合放进MySQL的数据
需要放进缓存的数据
Schema设计对系统的性能影响
尽量减少对数据库访问的请求
尽量减少无用数据的查询请求
硬件环境对系统性能的影响
性能分析
MySQL Query Optimizer
MySQL 中有专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是 DBA 认为是最优的,这部分最耗费时间)
当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimize r时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
MySQL常见瓶颈
CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
性能下降SQL慢 执行时间长 等待时间长 原因分析
查询语句写的烂
索引失效(单值、复合)
关联查询太多join(设计缺陷或不得已的需求)
服务器调优及各个参数设置(缓冲、线程数等)
MySQL常见性能分析手段
慢查询日志
EXPLAIN 分析查询
profiling分析
show命令查询系统状态及系统变量
性能瓶颈定位
通过 show 命令查看 MySQL 状态及变量,找到系统的瓶颈
Mysql> show status ——显示状态信息(扩展show status like ‘XXX’)
Mysql> show variables ——显示系统变量(扩展show variables like ‘XXX’)
Mysql> show innodb status ——显示InnoDB存储引擎的状态
Mysql> show processlist ——查看当前SQL执行,包括执行状态、是否锁表等
Shell> mysqladmin variables -u username -p password——显示系统变量
Shell> mysqladmin extended-status -u username -p password——显示状态信息
Explain(执行计划)
xplain 关键字可以模拟优化器执行SQL查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈
能干吗
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
怎么玩
Explain + SQL语句
执行计划包含的信息(如果有分区表的话还会有partitions)
各字段解释
id
(select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序)
select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序)
id相同,执行顺序从上往下
id全不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
id部分相同,执行顺序是先按照数字大的先执行,然后数字相同的按照从上往下的顺序执行
select_type
(查询类型,用于区别普通查询、联合查询、子查询等复杂查询)
SIMPLE :简单的select查询,查询中不包含子查询或UNION
PRIMARY:查询中若包含任何复杂的子部分,最外层查询被标记为PRIMARY
SUBQUERY:在select或where列表中包含了子查询
DERIVED:在from列表中包含的子查询被标记为DERIVED,MySQL会递归执行这些子查询,把结果放在临时表里
UNION:若第二个select出现在UNION之后,则被标记为UNION,若UNION包含在from子句的子查询中,外层select将被标记为DERIVED
UNION RESULT:从UNION表获取结果的select
table(显示这一行的数据是关于哪张表的)
type
显示查询使用了那种类型,从最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL )
tip: 一般来说,得保证查询至少达到range级别,最好到达ref
system:表只有一行记录(等于系统表),是 const 类型的特例,平时不会出现
const:表示通过索引一次就找到了,const 用于比较 primary key 或 unique 索引,因为只要匹配一行数据,所以很快,如将主键置于 where 列表中,mysql 就能将该查询转换为一个常量
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
ref:非唯一性索引扫描,范围匹配某个单独值得所有行。本质上也是一种索引访问,他返回所有匹配某个单独值的行,然而,它可能也会找到多个符合条件的行,多以他应该属于查找和扫描的混合体
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需开始于索引的某一点,而结束于另一点,不用扫描全部索引
index:Full Index Scan,index于ALL区别为index类型只遍历索引树。通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
ALL:Full Table Scan,将遍历全表找到匹配的行
possible_keys(显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用)
key
实际使用的索引,如果为NULL,则没有使用索引
查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠,仅出现在key列表中
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
ref
(显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值)
rows
(根据表统计信息及索引选用情况,大致估算找到所需的记录所需要读取的行数)
Extra(包含不适合在其他列中显示但十分重要的额外信息)
using filesort: 说明mysql会对数据使用一个外部的索引排序,不是按照表内的索引顺序进行读取。mysql中无法利用索引完成的排序操作称为“文件排序”。常见于order by和group by语句中
Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错,如果同时出现using where,表明索引被用来执行索引键值的查找;否则索引被用来读取数据而非执行查找操作
using where:使用了where过滤
using join buffer:使用了连接缓存
impossible where:where子句的值总是false,不能用来获取任何元祖
select tables optimized away:在没有group by子句的情况下,基于索引优化操作或对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化
distinct:优化distinct操作,在找到第一匹配的元祖后即停止找同样值的动作
慢查询日志
MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阈值的语句,具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。
long_query_time 的默认值为10,意思是运行10秒以上的语句
默认情况下,MySQL数据库没有开启慢查询日志,需要手动设置参数开启
查看开启状态
SHOW VARIABLES LIKE '%slow_query_log%'
也可使用 pt-query-digest 分析 RDS MySQL 慢查询日志
Show Profile 分析查询
通过慢日志查询可以知道哪些 SQL 语句执行效率低下,通过 explain 我们可以得知 SQL 语句的具体执行情况,索引使用等,还可以结合Show Profile命令查看执行状态。
Show Profile 是 MySQL 提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
默认情况下,参数处于关闭状态,并保存最近15次的运行结果
查询命令: show profiles;
分析
converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
create tmp table 创建临时表,这个要注意
Copying to tmp table on disk 把内存临时表复制到磁盘
locked
诊断SQL,show profile cpu,block io for query id(上一步前面的问题SQL数字号码)
日常开发需要注意的结论
看看当前的mysql版本是否支持
Show variables like 'profiling'; --默认是关闭,使用前需要开启
开启命令
set profiling=1;
索引优化
全值匹配我最爱
最佳左前缀法则,比如建立了一个联合索引(a,b,c),那么其实我们可利用的索引就有(a), (a,b), (a,b,c)
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
存储引擎不能使用索引中范围条件右边的列
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select
is null ,is not null 也无法使用索引
like "xxxx%" 是可以用到索引的,like "%xxxx" 则不行(like "%xxx%" 同理)。like以通配符开头('%abc...')索引失效会变成全表扫描的操作,
字符串不加单引号索引失效
少用or,用它来连接时会索引失效
<,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 则不行,会导致全表扫描
一般性建议
对于单键索引,尽量选择针对当前query过滤性更好的索引
在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
少用Hint强制索引
查询优化
永远小标驱动大表(小的数据集驱动大的数据集)
slect * from A where id in (select id from B)`等价于
#等价于
select id from B
select * from A where A.id=B.id
当 B 表的数据集必须小于 A 表的数据集时,用 in 优于 exists
select * from A where exists (select 1 from B where B.id=A.id)
#等价于
select * from A
select * from B where B.id = A.id`
当 A 表的数据集小于B表的数据集时,用 exists优于用 in
order by关键字优化
order by子句,尽量使用 Index 方式排序,避免使用 FileSort 方式排序
MySQL 支持两种方式的排序,FileSort 和 Index,Index效率高,它指 MySQL 扫描索引本身完成排序,FileSort 效率较低;
ORDER BY 满足两种情况,会使用Index方式排序;①ORDER BY语句使用索引最左前列 ②使用where子句与ORDER BY子句条件列组合满足索引最左前列
尽可能在索引列上完成排序操作,遵照索引建的最佳最前缀
如果不在索引列上,filesort 有两种算法,mysql就要启动双路排序和单路排序
双路排序:MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据
单路排序:从磁盘读取查询需要的所有列,按照order by 列在 buffer对它们进行排序,然后扫描排序后的列表进行输出,效率高于双路排序
优化策略
增大sort_buffer_size参数的设置
增大max_lencth_for_sort_data参数的设置
GROUP BY关键字优化
group by实质是先排序后进行分组,遵照索引建的最佳左前缀
当无法使用索引列,增大 max_length_for_sort_data 参数的设置,增大sort_buffer_size参数的设置
where高于having,能写在where限定的条件就不要去having限定了
数据类型优化
更小的通常更好:一般情况下,应该尽量使用可以正确存储数据的最小数据类型。
简单就好:简单的数据类型通常需要更少的CPU周期。例如,整数比字符操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型比较复杂。
尽量避免NULL:通常情况下最好指定列为NOT NULL
降低范式标准,增加冗余
面试题
日常工作中你是怎么优化SQL的?
SQL优化的一般步骤是什么,怎么看执行计划(explain),如何理解其中各个字段的含义?
如何写sql能够有效的使用到复合索引?
一条sql执行过长的时间,你如何优化,从哪些方面入手?
什么是最左前缀原则?什么是最左匹配原则?
查询中哪些情况不会使用索引?
分区分库分表
MySQL分区
一般情况下我们创建的表对应一组存储文件,使用MyISAM存储引擎时是一个.MYI和.MYD文件,使用Innodb存储引擎时是一个.ibd和.frm(表结构)文件。
能干嘛
逻辑数据分割
提高单一的写和读应用速度
提高分区范围读查询的速度
分割数据能够有多个不同的物理文件路径
高效的保存历史数据
怎么玩
查看是否支持分区
MySQL5.6以及之前版本
SHOW VARIABLES LIKE '%partition%';
MySQL5.6
show plugins;
分区类型及操作
RANGE分区
基于属于一个给定连续区间的列值,把多行分配给分区。mysql将会根据指定的拆分策略,,把数据放在不同的表文件上。相当于在文件上,被拆成了小块.但是,对外给客户的感觉还是一张表,透明的。
好处:扩容的时候很简单
LIST分区
类似于按RANGE分区,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合
HASH分区
基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式
KEY分区
类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值
分区缺点
分区键设计不太灵活,如果不走分区键,很容易出现全表锁
一旦数据并发量上来,如果在分区表实施关联,就是一个灾难
自己分库分表,自己掌控业务场景与访问模式,可控。分区表,研发写了一个sql,都不确定mysql是怎么玩的,不太可控
MySQL分表
垂直拆分
通常是按照业务功能的使用频次,把主要的、热门的字段放在一起做为主要表。然后把不常用的,按照各自的业务属性进行聚集,拆分到不同的次要表中;主要表和次要表的关系一般都是一对一的
水平拆分(数据分片)
单表的容量不超过500W,否则建议水平拆分。是把一个表复制成同样表结构的不同表,然后把数据按照一定的规则划分,分别存储到这些表中,从而保证单表的容量不会太大,提升性能;当然这些结构一样的表,可以放在一个或多个数据库中
水平分割的几种方法
使用MD5哈希,做法是对UID进行md5加密,然后取前几位(我们这里取前两位),然后就可以将不同的UID哈希到不同的用户表(user_xx)中了。
还可根据时间放入不同的表,比如:article_201601,article_201602。
按热度拆分,高点击率的词条生成各自的一张表,低热度的词条都放在一张大表里,待低热度的词条达到一定的贴数后,再把低热度的表单独拆分成一张表。
根据ID的值放入对应的表,第一个表user_0000,第二个100万的用户数据放在第二 个表user_0001中,随用户增加,直接添加用户表就行了。
MySQL分库
优点
减少增量数据写入时的锁对查询的影响
由于单表数量下降,常见的查询操作由于减少了需要扫描的记录,使得单表单次查询所需的检索行数变少,减少了磁盘IO,时延变短,但是它无法解决单表数据量太大的问题
分库分表后的难题
分布式事务的问题,数据的完整性和一致性问题
数据操作维度问题:用户、交易、订单各个不同的维度,用户查询维度、产品数据分析维度的不同对比分析角度。跨库联合查询的问题,可能需要两次查询 跨节点的count、order by、group by以及聚合函数问题,可能需要分别在各个节点上得到结果后在应用程序端进行合并 额外的数据管理负担,如:访问数据表的导航定位 额外的数据运算压力,如:需要在多个节点执行,然后再合并计算程序编码开发难度提升,没有太好的框架解决,更多依赖业务看如何分,如何合,是个难题。
分库分表(先垂直,后水平:简单、符合处理现实问题的方式)
https://blog.csdn.net/qq_39940205/article/details/80536666
垂直切分
垂直分库
根据业务拆分:用户、商品、订单 等(分库后用不同服务器:磁盘、内存、tps)
垂直分表
大表拆小表(基于字段,将不常用、数据较大、字段较长的字段拆分到扩展表)
水平切分
水平分表
针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去。
但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用
水平分库分表
将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈
水平分库分表切分规则
RANGE
HASH取模
地理区域
时间
问题
分布式ID
UUID
数据库自增长序列
snowflake
redis incr命令实现id自增
跨表操作
分布式事务
扩容
面试题
随着业务的发展,业务越来越复杂,应用的模块越来越多,总的数据量很大,高并发读写操作均超过单个数据库服务器的处理能力怎么办?
数据分片,数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中。数据分片的有效手段就是对关系型数据库进行分库和分表。
区别于分区的是,分区一般都是放在单机里的,用的比较多的是时间范围分区,方便归档。只不过分库分表需要代码实现,分区则是mysql内部实现。分库分表和分区并不冲突,可以结合使用。
说说分库与分表的设计
为什么要分库?
数据库集群环境后都是多台 slave,基本满足了读取操作; 但是写入或者说大数据、频繁的写入操作对master性能影响就比较大,这个时候,单库并不能解决大规模并发写入的问题,所以就会考虑分库。
分库是什么?
一个库里表太多了,导致了海量数据,系统性能下降,把原本存储于一个库的表拆分存储到多个库上, 通常是将表按照功能模块、关系密切程度划分出来,部署到不同库上。
主从复制
复制的基本原理
slave 会从 master 读取 binlog 来进行数据同
三个步骤
master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
salve 将 master 的 binary log events 拷贝到它的中继日志(relay log);
slave 重做中继日志中的事件,将改变应用到自己的数据库中。MySQL 复制是异步且是串行化的。
复制的基本原则
每个 slave只有一个 master
每个 salve只能有一个唯一的服务器 ID
每个master可以有多个salve
复制的最大问题
延时
EXPLAIN解释器
type
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
SQL性能优化的目标,至少要达到range 级别,要求是ref级别,如果可以是const最好
MySQL日志种类
错误日志
记录出错信息,也记录一些警告信息或者正确的信息
查询日志
记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行
慢查询日志
设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中
二进制日志
记录对数据库执行更改的所有操作
中继日志
中继日志也是二进制日志,用来给slave 库恢复
事务日志
重做日志redo和回滚日志undo
说一说三个范式
第一范式(1NF)
数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等
第二范式(2NF)
数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字
第三范式(3NF)
在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存
依赖关系:关键字段 → 非关键字段 x → 非关键字段y
百万级别或以上的数据如何删除
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的
所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
然后删除其中无用数据(此过程需要不到两分钟)
删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了
06.IO与网络编程
基本IO
文件IO
序列化
网络IO
socket
线程与并发
线程
锁
线程安全
原子操作性
Synchronized
同步与异步
共享与可见性
NIO
并发容器
Executor框架
线程池
newStringThreadExecutor
newFixdTheadPool
newCachedThreadPool
newScheduledThreadPool
延迟与周期任务
ScheduledThreadPoolExecutor
Callable 与 Future
BlockingQueue
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue
PriorityBlockingQueue
SynchronousQueue
并发
同步、异步、阻塞、非阻塞
Fork/Join
消息队列
ActiveMQ
IO设计模式
Reactor
Proactor
NIO和IO的主要区别
IO是面向流的,NIO是面向缓冲区的
Java IO的各种流是阻塞的,Java NIO的非阻塞模式
Java NIO的选择器允许一个单独的线程来监视多个输入通道
网络并发
数据结构
XML
DOM
SAX
JSON
fastjson
gjson
通信协议
HTTP/HTTPS
https原理:https://www.cnblogs.com/zery/p/5164795.html
XMPP
socket
通信框架
MINA
Netty
网络基础
TCP/IP
链路层
主要是网卡、操作系统、驱动 等软硬件设备
网络层
IP 协议,路由和寻址
传输层
TCP
三次握手
过程
SYN +客户端 seq序列号(ISN)
SYN+ACK+ACKnum+服务端seq序列号
ACK+ACKnum
为什么两次不行
四次挥手
有连接
可靠传输
重发
排序
UDP
无连接
丢包
速度快,占资源少
适用于能忍受丢包的情景,如视频、语音等
应用层
DNS
HTTP
FTP
IO多路复用
epoll
事件
select
轮询
Socket
Netty
07.消息队列
常见队列比较
kafka
领英开源
高吞吐、高并发、高性能
常用与超高吞吐量的日志采集、实时数据同步、实时数据计算等场景
RabbitMQ
Erlang开发
高吞吐、高并发、高性能
集群部署、高可用
消息可靠
后台管理界面完善
社区活跃度高
原理
Erlang实现的高级消息队列协议(AMQP)
AMQP
模型层(Model Layer)
会话层(Sessionn Layer)
传输层(Transport Layer)
RocketMQ
阿里开源
Java开发
高吞吐、高并发、高性能
支持分布式事务
场景和选型
削峰
秒杀场景
解耦
A系统生成数据,B C进行订阅
异步调用
下单和派送时两个过程
缺点
数据一致性
一般是最终一致性
系统稳定性降低
重复数据和数据丢失
系统可用性降低
依赖MQ的高可用
08.中间件
8.高并发
锁
happens-before
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系
原则定义
如果一个操作happens-before 另一个操作,那么第一个操作的执行结果 将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前
两个操作之间存在happens-before 关系,并不意味着 一定要按照happens-before 原则制定的顺序来执行。如果重排序之后的执行结果 与按照happens-before关系来执行的结果一致,那么这种重排序并不非法
规则
1.程序顺序规则:一个线程中的每个操作,happens-before于该线程的任意后续操作
2.监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
3.Volatile变量规则:对一个volatile的写,happens-before于任意后续对这个volatile的读
4.传递性
5.start()规则:如果线程A执行操作ThreadB.start(),那么线程A的ThreadB.start 操作happens-before于线程B中的任意操作
6.join()规则:如果线程A执行操作ThreadB.join(),,并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回
Synchronized
尽量减少同步块
官方提示相对Lock 优先使用Synchronized
非公平锁
1.6优化之后 性能跟重入锁几乎一样
偏向锁
轻量级锁
自选锁
AQS实现原理
Lock
ReentrantLock
实现原理
ReentrantReadWriteLock
对锁可以多线程同时获取
上写锁之后,其他线程读锁和写锁都获取不到
读锁共享,写锁独占
Volatile
内存可见性
指令重排
concurrent包中的工具类大多使用volatile 和CAS配合实现
重排序规则
1.当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序
2.当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
3.当第一个操作是volatile写,第二个是volatile读时,不能重排序
悲观锁
悲观的认为每次获取数据之后,别人都会把原始数据改掉,所以每次操作都会加锁,防止期间 数据被别人改掉
使用写频繁的场景
乐观锁
乐观的认为读取数据之后,没有人会修改原始数据,但是更新数据时 需要判断原始数据有没有人修改富哦,没有才写入,改过则丢弃
使用读频繁的场景
一般使用CAS或者版本号来实现
缺点
ABA问题
CAS
概念
compare and swap 比较并交换(是乐观锁)
过程
V内存原值
A 预期原值
B新值
用预期原值A与内存值V比较,如果相等 使用新值B替换V,不相等 就无操作
死锁
避免一个线程 在锁内同时占用多个资源,尽量保证每个锁 只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
线程池
Executor
Executors
ThreadPoolExecutor
参数
corePoolSize(核心线程数,一直存活,默认1)
核心线程会一直存活,及时没有任务需要执行
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
queueCapacity(阻塞队列数量,默认Integer.MAX_VALUE)
当核心线程数达到最大时,新任务会放在队列中排队等待执行
maxPoolSize(最大线程数量,Integer.MAX_VALUE)
当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
keepAliceTime(线程空闲时间,默认60s)
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
allowCoreThreadTimeOut(允许核心线程超时,默认 false)
为true 时核心线程空闲时间超过keepAliveTime也会被回收
rejectedExecutionHandler(任务拒绝处理器,默认AbortPolicy())
当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务.
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
如何来设置参数
需要根据几个值来决定
tasks :每秒的任务数,假设为500~1000
taskcost:每个任务花费时间,假设为0.1s
responsetime:系统允许容忍的最大响应时间,假设为1s
做几个计算
corePoolSize = 每秒需要多少个线程处理?
threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
queueCapacity = (coreSizePool/taskcost)*responsetime
计算可得 queueCapacity = 80/0.1*1 = 800。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
调优
Runnable
入口run方法,无返回值
Callable
入口call方法,有返回值Future泛型可以用来获取异步执行结果
Thread
FutureTask
上下文切换
频繁切换会降低性能
CPU控制权由一个正在运行的线程切换到另一个已经就绪等待获取CPU控制权的线程的
配置规则
IO密集
指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理,即在这个时间范围内,可以由其他线程来使用CPU
多配置一些线程
CPU密集
系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成
配置CPU处理器个数+/-1个线程
混合型
两者都占有一定的时间
配置规则
https://segmentfault.com/a/1190000004249911(待整理)
多线程
程间通信协作
现实中,需要线程之间的协作。
比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作
生产-消费者 案例:https://www.cnblogs.com/xdyixia/p/9386133.html
线程通信协作的最常见的两种方式
syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
通过管道进行线程间通信:1)字节流;2)字符流
实现多线程编程的方式有两种
继承 Thread 类
实现 Runnable 接口
进程间的通讯方式
管道
有名管道
信号量
消息队列
信号
共享内存
套接字
ThreadLocal
ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险
每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal
ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置(加1或减1)
Key是弱引用类型的,Value并非弱引用
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收
队列
阻塞队列
有界
ArrayBlockingQueue
LinkedBlockingQueue
无界
PriorityBlockingQueue
支持优先级
DelayQueue
使用优先队列实现
LinkedTransferQueue
LinkedBlockingDeque
SynchronousQueue
并发队列
ConcurrentLinkedQueue
使用CAS,poll性能提升较多
concurrent
ConcurrentHashMap
JDK7
分段锁-Segment(默认16个Segment)
自旋锁
每个Segment都是一个数组,hash碰撞之后 使用链表
JDK8
大数组
hash碰撞之后使用链表(链表长度大于8时,使用红黑树存储)
CAS+Synchronized
CountDownLatch
不能重用,只能指定一次 计数次数
主要方法
await
调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断
countDown
调用此方法则计数减1
底层原理
AQS(AbstractQueuedSynchronizer)里面的共享锁来实现
使用场景
开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段
有任务A和任务B,任务B必须在任务A完成之后再做。而任务A还能被分为n部分,并且这n部分之间的任务互不影响。为了加快任务完成进度,把这n部分任务分给不同的线程,当A任务完成了,然后通知做B任务的线程接着完成任务,至于完成B任务的线程,可以是一个,也可以是多个。
CyclicBarrier
可重用
CountDownLatch 和CyclicBarrier的不同之处?
Semaphore
acquire
获取许可
release
释放许可
原子工具类
使用CAS和Volatile实现
解决方案
扩容
垂直扩容
水平扩容
应用拆分
集群化
分布式
限流
数据库
分库分表
读写分离
消息队列
异步化
解耦
削峰
流量控制
缓存
分类和场景
分布式缓存
redis
本地缓存
ehcache
map
集中式缓存
memcache
常见问题
缓存击穿
热点数据
缓存雪崩
缓存击穿
一致性
过期策略
LRU
Least Recently Userd 最近最少使用
实现
可以使用双向链表实现,最近使用的数据移动到尾部,超过长度删除头部数据
LinkedHashMap 中可以按照访问顺序排序,原理也是双向链表,每次访问都会数据移动到尾部,然后重写removeEldestEntry方法即可,removeEldestEntry是否移除老数据的方法,默认false
IO
创建和系统核数相同的线程
volatile、synchronized、lock的区别
synchronized和volatile的区别
volatile 不会发生线程阻塞,而 synchronized 会发生线程阻塞
volatile 只能修饰变量,而 synchronized 可以修饰方法、代码块等。
volatile 不能保证原子性(不能保证线程安全),而 synchronized 可以保证原子性。
volatile 解决的是变量在多线程之间的可见性,而 synchronized 解决的是多线程之间访问资源的同步性
synchronized和lock的区别
synchronized是隐式锁,在需要同步的对象中加入此控制,而lock是显示锁,需要显示指定起始位置和终止位置
使用lock时在finally中必须释放锁,不然容易造成线程死锁;而使用synchronized时,获取锁的线程会在执行完同步代码后释放锁(或者JVM会在线程执行发生异常时释放锁)
使用lock时线程不会一直等待;而使用synchronized时,假设A线程获得锁后阻塞,其他线程会一直等待
lock可重入、可中断、可公平也可不公平;而synchronized可重入但不可中断、非公平
JDK1.8新特性
CompletableFuture
Stream
09.缓存
Redis
定位
内存型数据库,nosql 数据库
缓存
分布式锁
加锁
SET
支持NX、PX命令、可以在Set的时候传入
SETNX
set if not exist
key 和 expire需要同时设置,保证原子操作,expir 保证不会出现死锁
解锁
脚本
要保证只能释放自己的锁
get 和 del 要有原子性
String script=“if redis.ca.ll('get',KEYS[1])==ARG[1] then return redis.call('del'),KEYS[1] else return 0 end”
常用命令
set
get
setnx
SET if Not Exists
getset
将给定 key 的值设为 value ,并返回 key 的旧值(old value)
incr
Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作
Redis原子计数器
Watch/UNWATCH
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。
监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)
MULTI/EXEC/DISCARD
开启一个事务;执行事务;放弃事务
scan 搜索
特性
原子性
单线程
持久化
基于语句追加(AOF)
定时快照(RDB)
支持订阅-发布
支持过期
基本数据结构
String (字符串)
List(队列)
Hash(哈希)
Sets (集合)
Sorted Sets(有序集合)
Redis原理
线程IO模型
多并发
非异步IO+事件轮询API(多路复用,相当于JAVA里面的NIO )
指令队列
定时队列
持久化
RDB持久化
原理是将Reids在内存中的数据库记录定时dump到磁盘上
定时执行(fork子进程)
AOF持久化(append only file)
原理是将Reids的操作(写、删除)日志以追加的方式写入文件
Redis集群
3.0版本以上
数据分片(sharding)
使用分片(sharding)实现,总共有16384个哈希槽(hash slot)
key 按照算法分配到不同的哈希槽
每个节点包含不同的哈希槽
主从复制模型
每个节点都有一主多从
数据一致性
不保证数据的强一致性
异步复制
回收策略
volatile-lru
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl
从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random
从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random
从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐)
禁止驱逐数据
使用策略规则
如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
Redis与Memcached的区别
Redis支持多种数据结构:如 list,set,zset,hash
Redis支持数据的备份
Redis支持数据的持久化
常见问题
缓存击穿问题
是请求很多缓存中不存在的数据(热点key),导致这些请求短时间内直接落在了数据库上,致使数据库异常
解决方案
接口限流与熔断、降级
缓存穿透
是指查询一个数据库一定不存在的数据
解决方案
布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从避免了对底层存储系统的查询压力
即使查询返回的数据为空,也会进行缓存。(不过 缓存时间较短,最长不超过5分钟)
布隆过滤器原理
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
子主题 3
如何选择哈希函数个数和布隆过滤器长度?
缓存雪崩问题
是指在某一个时间段,缓存集中过期失效
解决方案
备用缓存(Redis集群)
使用互斥锁
在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量
不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布
永不过期(简单高效)
高可用
主从模式
原理
https://www.jianshu.com/p/40212051ccc9
同步方式
全量(SYNC、BGSAVE)
增量
脑裂问题
只要问题:数据不一致,数据丢失
哨兵模式
选举master方式
集群模式
10.分布式
CAP理论
C(一致性)A(可用性)P(分区容错性)
任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项
BASE理论
分布式锁
当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数
实现方式
基于数据库
主键
版本号
排他锁(for update)
基于Redis
setnx()+expire()
setnx()+get()+getset()
1. setnx(lockkey, 当前时间+过期超时时间),如果返回 1,则获取锁成功;如果返回 0 则没有获取到锁,转向 2
2. get(lockkey) 获取值 oldExpireTime ,并将这个 value 值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向 3
3. 计算 newExpireTime = 当前时间+过期超时时间,然后 getset(lockkey, newExpireTime) 会返回当前 lockkey 的值currentExpireTime
4. 判断 currentExpireTime 与 oldExpireTime 是否相等,如果相等,说明当前 getset 设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试
5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行 delete 释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理
基于Redlock
基于 ZooKeeper 做分布式锁
ZK基本锁
利用临时节点与 watch 机制。每个锁占用一个普通节点 /lock,当需要获取锁时在 /lock 目录下创建一个临时节点,创建成功则表示获取锁成功,失败则 watch/lock 节点,有删除操作后再去争锁。
临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁
缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大
ZK优化锁
上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,如果这个节点序号不是最小的则 watch 序号比本身小的前一个节点 (公平锁)
步骤:
1.在 /lock 节点下创建一个有序临时节点 (EPHEMERAL_SEQUENTIAL)
2.判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后 watch 序号比本身小的前一个节点
3.当取锁失败,设置 watch 后则等待 watch 事件到来后,再次判断是否序号最小
4.取锁成功则执行代码,最后释放锁(删除该节点)
集群模式的 Redis 分布式锁,它基于 N 个完全独立的 Redis 节点(通常情况下 N 可以设置成 5)
优点:性能高
缺点:
失效时间设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间
基于redisson
分布式事务
Redis事务
两阶段提交(2PC)
补偿事务(TCC)
本地消息表(异步确保)
MQ 事务消息
消息队列
防止消息重复消费
消息的可靠性投递
0 条评论
下一页